com.nordstrom.automation.selenium.SeleniumConfig Maven / Gradle / Ivy
Show all versions of selenium3-foundation Show documentation
package com.nordstrom.automation.selenium;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.configuration2.io.FileLocationStrategy;
import org.apache.commons.configuration2.io.FileLocator;
import org.apache.commons.configuration2.io.FileLocatorUtils;
import org.apache.commons.configuration2.io.FileSystem;
import org.apache.commons.io.IOUtils;
import org.openqa.grid.internal.utils.configuration.GridHubConfiguration;
import org.openqa.grid.internal.utils.configuration.GridNodeConfiguration;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.SearchContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nordstrom.automation.selenium.support.SearchContextWait;
import com.nordstrom.automation.settings.SettingsCore;
import com.nordstrom.common.file.PathUtils;
/**
* This class declares settings and methods related to Selenium WebDriver and Grid configuration.
*
* @see SettingsCore
*/
public class SeleniumConfig extends SettingsCore {
private static final String SETTINGS_FILE = "settings.properties";
private static final String JSON_HEAD = "{ \"capabilities\": [";
private static final String JSON_TAIL = "] }";
private static final String CAPS_PATTERN = "{\"browserName\": \"%s\"}";
private static final Logger LOGGER = LoggerFactory.getLogger(SeleniumConfig.class);
/** value: {"browserName": "phantomjs"} */
private static final String DEFAULT_CAPS = String.format(CAPS_PATTERN, "phantomjs");
/** value: 5555 */
private static final Integer DEFAULT_NODE_PORT = Integer.valueOf(5555);
/**
* This enumeration declares the settings that enable you to control the parameters used by
* Selenium Foundation.
*
* Each setting is defined by a constant name and System property key. Many settings also define
* default values. Note that all of these settings can be overridden via the
* {@code settings.properties} file and System property declarations.
*/
public enum SeleniumSettings implements SettingsCore.SettingsAPI {
/** name: selenium.target.scheme
default: http */
TARGET_SCHEME("selenium.target.scheme", "http"),
/** name: selenium.target.creds
default: {@code null} */
TARGET_CREDS("selenium.target.creds", null),
/** name: selenium.target.host
default: localhost */
TARGET_HOST("selenium.target.host", "localhost"),
/** name: selenium.target.port
default: {@code null} */
TARGET_PORT("selenium.target.port", null),
/** name: selenium.target.path
default: / */
TARGET_PATH("selenium.target.path", "/"),
/** name: selenium.hub.config
default: hubConfig.json */
HUB_CONFIG("selenium.hub.config", "hubConfig.json"),
/** name: selenium.hub.host
default: {@code null} */
HUB_HOST("selenium.hub.host", null),
/** name: selenium.hub.port
default: {@code null} */
HUB_PORT("selenuim.hub.port", null),
/** name: selenium.node.config
default: nodeConfig.json */
NODE_CONFIG("selenium.node.config", "nodeConfig.json"),
/** name: selenium.node.host
default: {@code null} */
NODE_HOST("selenium.node.host", null),
/** name: selenium.node.port
default: 5555 */
NODE_PORT("selenium.node.port", "5555"),
/** name: selenium.browser.name
default: {@code null} */
BROWSER_NAME("selenium.browser.name", null),
/** name: selenium.browser.caps
default: {@link SeleniumConfig#DEFAULT_CAPS DEFAULT_CAPS} */
BROWSER_CAPS("selenium.browser.caps", DEFAULT_CAPS),
/** name: selenium.timeout.pageload
default: 30 */
PAGE_LOAD_TIMEOUT("selenium.timeout.pageload", "30"),
/** name: selenium.timeout.implied
default: 15 */
IMPLIED_TIMEOUT("selenium.timeout.implied", "15"),
/** name: selenium.timeout.script
default: 30 */
SCRIPT_TIMEOUT("selenium.timeout.script", "30"),
/** name: selenium.timeout.wait
default: 15 */
WAIT_TIMEOUT("selenium.timeout.wait", "15"),
/** name: selenium.timeout.host
default: 30 */
HOST_TIMEOUT("selenium.timeout.host", "30"),
/** name: google.dns.socket.host
default: 8.8.8.8 */
GOOGLE_DNS_SOCKET_HOST("google.dns.socket.host", "8.8.8.8"), //NOSONAR
/** name: google.dns.socket.port
default: 10002 */
GOOGLE_DNS_SOCKET_PORT("google.dns.socket.port", "10002");
private String propertyName;
private String defaultValue;
SeleniumSettings(String propertyName, String defaultValue) {
this.propertyName = propertyName;
this.defaultValue = defaultValue;
}
@Override
public String key() {
return propertyName;
}
@Override
public String val() {
return defaultValue;
}
}
/**
* This enumeration provides easy access to the timeout intervals defined in {@link SeleniumSettings}.
*/
public enum WaitType {
/**
* purpose: The maximum allowed interval for a page to finish loading.
* setting: {@link SeleniumSettings#PAGE_LOAD_TIMEOUT page load timeout}
*/
PAGE_LOAD(SeleniumSettings.PAGE_LOAD_TIMEOUT),
/**
* purpose: The maximum amount of time the driver will search for an element.
* setting: {@link SeleniumSettings#IMPLIED_TIMEOUT implicit timeout}
*/
IMPLIED(SeleniumSettings.IMPLIED_TIMEOUT),
/**
* purpose: The maximum allowed interval for an asynchronous script to finish.
* setting: {@link SeleniumSettings#SCRIPT_TIMEOUT script timeout}
*/
SCRIPT(SeleniumSettings.SCRIPT_TIMEOUT),
/**
* purpose: The maximum amount of time to wait for a search context event.
* setting: {@link SeleniumSettings#WAIT_TIMEOUT wait timeout}
*/
WAIT(SeleniumSettings.WAIT_TIMEOUT),
/**
* purpose: The maximum amount of time to wait for a Grid server to launch.
* setting: {@link SeleniumSettings#HOST_TIMEOUT host timeout}
*/
HOST(SeleniumSettings.HOST_TIMEOUT);
private SeleniumSettings timeoutSetting;
private Long timeoutInterval;
WaitType(SeleniumSettings timeoutSetting) {
this.timeoutSetting = timeoutSetting;
}
/**
* Get the timeout interval for this wait type
*
* @return wait type timeout interval
*/
public long getInterval() {
return getInterval(getConfig());
}
/**
* Get the timeout interval for this wait type.
*
* @param config {@link SeleniumConfig} object to interrogate
* @return wait type timeout interval
*/
public long getInterval(SeleniumConfig config) {
if (timeoutInterval == null) {
Objects.requireNonNull(config, "[config] must be non-null");
timeoutInterval = config.getLong(timeoutSetting.key());
}
return timeoutInterval;
}
/**
* Get a search context wait object for the specified context
*
* @param context context for which timeout is needed
* @return {@link SearchContextWait} object for the specified context
*/
public SearchContextWait getWait(SearchContext context) {
return new SearchContextWait(context, getInterval());
}
}
private static final SeleniumConfig seleniumConfig;
static {
try {
seleniumConfig = new SeleniumConfig();
} catch (ConfigurationException | IOException e) {
throw new RuntimeException("Failed to instantiate settings", e);
}
}
private URI targetUri;
private String nodeConfigPath;
private GridNodeConfiguration nodeConfig;
private String[] nodeArgs;
private String hubConfigPath;
private GridHubConfiguration hubConfig;
private String[] hubArgs;
private Capabilities browserCaps;
/**
* Instantiate a Selenium Foundation configuration object.
*
* @throws ConfigurationException If a failure is encountered while initializing this configuration object.
* @throws IOException If a failure is encountered while reading from a configuration input stream.
*/
public SeleniumConfig() throws ConfigurationException, IOException {
super(SeleniumSettings.class);
}
/**
* Get the Selenium configuration object.
*
* @return Selenium configuration object
*/
public static SeleniumConfig getConfig() {
return seleniumConfig;
}
/**
* Get the configured target URI as specified by its component parts.
*
* NOTE: The target URI is assembled from following components:
* {@link SeleniumSettings#TARGET_SCHEME scheme}, {@link SeleniumSettings#TARGET_CREDS credentials},
* {@link SeleniumSettings#TARGET_HOST host}, {@link SeleniumSettings#TARGET_PORT port}, and
* {@link SeleniumSettings#TARGET_PATH base path}
*
* @return assembled target URI
*/
public URI getTargetUri() {
if (targetUri == null) {
UriBuilder builder = UriBuilder.fromPath(getString(SeleniumSettings.TARGET_PATH.key()))
.scheme(getString(SeleniumSettings.TARGET_SCHEME.key()))
.host(getString(SeleniumSettings.TARGET_HOST.key()));
String creds = getString(SeleniumSettings.TARGET_CREDS.key());
if (creds != null) {
builder.userInfo(creds);
}
String port = getString(SeleniumSettings.TARGET_PORT.key());
if (port != null) {
builder.port(Integer.parseInt(port));
}
targetUri = builder.build();
}
return targetUri;
}
/**
* Get the path to the Selenium Grid node configuration.
*
* @return Selenium Grid node configuration path
*/
private String getNodeConfigPath() {
if (nodeConfigPath == null) {
nodeConfigPath = getConfigPath(getString(SeleniumSettings.NODE_CONFIG.key()));
LOGGER.debug("nodeConfigPath = {}", nodeConfigPath);
}
return nodeConfigPath;
}
/**
* Get the Selenium Grid node configuration.
*
* @return Selenium Grid node configuration
*/
public GridNodeConfiguration getNodeConfig() {
if (nodeConfig == null) {
nodeConfig = GridNodeConfiguration.loadFromJSON(getNodeConfigPath());
nodeConfig = resolveNodeSettings(nodeConfig);
}
return nodeConfig;
}
/**
* Get the arguments needed to launch a local Selenium Grid node.
*
* @return array of node launch arguments
*/
public String[] getNodeArgs() {
if (nodeArgs == null) {
String configPath = getNodeConfigPath();
GridNodeConfiguration config = getNodeConfig();
nodeArgs = new String[] {"-role", "node", "-nodeConfig", configPath, "-host",
config.host, "-port", config.port.toString(), "-hub", config.hub,
"-servlet", "org.openqa.grid.web.servlet.LifecycleServlet"};
}
return Arrays.copyOf(nodeArgs, nodeArgs.length);
}
/**
* Resolve Selenium Grid node settings for host, port, and hub.
*
* @param nodeConfig node configuration with unresolved settings
* @return node configuration with resolved settings
*/
private GridNodeConfiguration resolveNodeSettings(GridNodeConfiguration nodeConfig) {
String nodeHost = getString(SeleniumSettings.NODE_HOST.key());
if (nodeHost != null) {
nodeConfig.host = nodeHost;
}
if (nodeConfig.host == null) {
nodeConfig.host = getLocalHost();
}
nodeConfig.port = getInteger(SeleniumSettings.NODE_PORT.key(), DEFAULT_NODE_PORT);
nodeConfig.hub = "http://" + getHubConfig().host + ":" + getHubConfig().port + "/grid/register/";
return nodeConfig;
}
/**
* Get the path to the Selenium Grid hub configuration.
*
* @return Selenium Grid hub configuration path
*/
private String getHubConfigPath() {
if (hubConfigPath == null) {
hubConfigPath = getConfigPath(getString(SeleniumSettings.HUB_CONFIG.key()));
LOGGER.debug("hubConfigPath = {}", hubConfigPath);
}
return hubConfigPath;
}
/**
* Get the Selenium Grid hub configuration.
*
* @return Selenium Grid hub configuration
*/
public GridHubConfiguration getHubConfig() {
if (hubConfig == null) {
hubConfig = GridHubConfiguration.loadFromJSON(getHubConfigPath());
hubConfig = resolveHubSettings(hubConfig);
}
return hubConfig;
}
/**
* Get the arguments needed to launch a local Selenium Grid hub.
*
* @return array of hub launch arguments
*/
public String[] getHubArgs() {
if (hubArgs == null) {
String configPath = getHubConfigPath();
GridHubConfiguration config = getHubConfig();
hubArgs = new String[] {"-role", "hub", "-hubConfig", configPath,
"-host", config.host, "-port", config.port.toString()};
}
return Arrays.copyOf(hubArgs, hubArgs.length);
}
/**
* Resolve Selenium Grid hub settings for host and port.
*
* @param hubConfig node configuration with unresolved settings
* @return hub configuration with resolved settings
*/
private GridHubConfiguration resolveHubSettings(GridHubConfiguration hubConfig) {
String hubHost = getString(SeleniumSettings.HUB_HOST.key());
if (hubHost != null) {
hubConfig.host = hubHost;
}
if (hubConfig.host == null) {
hubConfig.host = getLocalHost();
}
Integer hubPort = getInteger(SeleniumSettings.HUB_PORT.key(), null);
if (hubPort != null) {
hubConfig.port = hubPort;
}
return hubConfig;
}
/**
* Get Internet protocol (IP) address for the machine we're running on.
*
* @return IP address for the machine we're running on (a.k.a. - 'localhost')
*/
private String getLocalHost() {
String host = getString(SeleniumSettings.GOOGLE_DNS_SOCKET_HOST.key());
int port = getInt(SeleniumSettings.GOOGLE_DNS_SOCKET_PORT.key());
try (final DatagramSocket socket = new DatagramSocket()) {
// use Google Public DNS to discover preferred local IP
socket.connect(InetAddress.getByName(host), port);
return socket.getLocalAddress().getHostAddress();
} catch (SocketException | UnknownHostException eaten) {
LOGGER.warn("Unable to get 'localhost' IP address: {}", eaten.getMessage());
return "localhost";
}
}
/**
* Convert the configured browser specification from JSON to {@link Capabilities} object.
*
* @return {@link Capabilities} object for the configured browser specification
*/
public Capabilities getBrowserCaps() {
if (browserCaps == null) {
String jsonStr = null;
String nameStr = getString(SeleniumSettings.BROWSER_NAME.key());
if (nameStr != null) {
InputStream inputStream =
Thread.currentThread().getContextClassLoader().getResourceAsStream(nameStr + "Caps.json");
if (inputStream != null) {
try {
jsonStr = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
} catch (IOException eaten) {
LOGGER.warn("Unable to get browser configuration file contents: {}", eaten.getMessage());
}
}
if (jsonStr == null) {
jsonStr = String.format(CAPS_PATTERN, nameStr);
}
}
if (jsonStr == null) {
jsonStr = getString(SeleniumSettings.BROWSER_CAPS.key());
}
JsonObject json = new JsonParser().parse(JSON_HEAD + jsonStr + JSON_TAIL).getAsJsonObject();
GridNodeConfiguration config = GridNodeConfiguration.loadFromJSON(json);
browserCaps = config.capabilities.get(0);
}
return browserCaps;
}
/**
* Get the path to the specified configuration file.
*
* @param path configuration file path (absolute, relative, or simple filename)
* @return resolved absolute path of specified file; 'null' if file not found
*/
private static String getConfigPath(String path) {
FileHandler handler = new FileHandler();
handler.setPath(path);
FileLocator locator = handler.getFileLocator();
FileSystem fileSystem = FileLocatorUtils.DEFAULT_FILE_SYSTEM;
FileLocationStrategy strategy = FileLocatorUtils.DEFAULT_LOCATION_STRATEGY;
URL url = strategy.locate(fileSystem, locator);
if (url != null) {
try {
URI uri = url.toURI();
if ("jar".equals(uri.getScheme())) {
try {
FileSystems.newFileSystem(uri, Collections.emptyMap());
} catch (FileSystemAlreadyExistsException eaten) {
LOGGER.warn("Specified file system already exists: {}", eaten.getMessage());
}
String outputDir = PathUtils.getBaseDir();
File outputFile = new File(outputDir, path);
Path outputPath = outputFile.toPath();
if (!outputPath.toFile().exists()) {
Files.copy(Paths.get(uri), outputPath);
}
uri = outputPath.toUri();
}
File file = new File(uri);
return file.getAbsolutePath();
} catch (URISyntaxException eaten) {
LOGGER.warn("Invalid URL returned by file locator: {}", eaten.getMessage());
} catch (IOException eaten) {
LOGGER.warn("Failed to construct file system or extract configuration file: {}", eaten.getMessage());
}
}
return null;
}
@Override
public String getSettingsPath() {
return SETTINGS_FILE;
}
}