Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.testable.selenium.TestableSelenium Maven / Gradle / Ivy
package io.testable.selenium;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.TreeMap;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Main entry point for creating a selenium driver for use on the Testable platform as well as a set of useful
* utilities like taking screenshots and reporting custom metrics.
*/
public class TestableSelenium {
public static final int SELENIUM_PORT = Integer.getInteger("SELENIUM_PORT", -1);
public static final String OUTPUT_DIR = System.getProperty("OUTPUT_DIR");
public static final String REGION_NAME = System.getProperty("TESTABLE_REGION_NAME");
public static final String GLOBAL_CLIENT_INDEX = System.getProperty("TESTABLE_GLOBAL_CLIENT_INDEX");
public static final String ITERATION = System.getProperty("TESTABLE_ITERATION");
public static final String PROXY_AUTOCONFIG_URL = System.getProperty("PROXY_AUTOCONFIG_URL");
public static final String CHROME_BINARY_PATH = System.getProperty("CHROME_BINARY_PATH");
public static final String FIREFOX_BINARY_PATH = System.getProperty("FIREFOX_BINARY_PATH");
public static final String EDGE_BINARY_PATH = System.getProperty("EDGE_BINARY_PATH");
public static final String PROFILE_DIR = System.getProperty("TESTABLE_PROFILE_DIR");
public static final String RESULT_FILE = System.getProperty("TESTABLE_RESULT_FILE");
public static final String DISPLAY_SIZE = System.getProperty("DISPLAY_SIZE");
public static final String USER_AGENT = System.getProperty("USER_AGENT");
public static final String SCALE_FACTOR = System.getProperty("SCALE_FACTOR");
private static String RUM_SPEEDINDEXJS;
static {
try {
URL url = Resources.getResource("rum-speedindex.js");
RUM_SPEEDINDEXJS = Resources.toString(url, Charsets.UTF_8);
} catch(Exception e) {
System.out.println("Issue reading rum-speedindex.js from file");
e.printStackTrace();
RUM_SPEEDINDEXJS = null;
}
}
private static PrintWriter resultStream;
static {
try {
resultStream = RESULT_FILE != null ? new PrintWriter(new FileWriter(RESULT_FILE, true)) : null;
} catch (IOException ioe) {
System.out.println("Issue writing to Testable result file");
ioe.printStackTrace();
resultStream = null;
}
}
/**
* Create a {@link RemoteWebDriver} instance that is compatible with Testable. All this means is that the URL
* will be http://localhost:[port]/wd/hub where [port] is passed from Testable to the Selenium process as
* the SELENIUM_PORT system property. When run locally outside Testable, the default Selenium port (4444) is used.
*
* @param capabilities Capabilities to utilize
* @return A WebDriver instance that is compatible with the local Testable Selenium instance.
*/
public static WebDriver newWebDriver(Capabilities capabilities) {
try {
Capabilities caps;
if (capabilities instanceof DesiredCapabilities) {
DesiredCapabilities desiredCapabilities = (DesiredCapabilities) capabilities;
String browserName = desiredCapabilities.getBrowserName();
if (browserName == null)
throw new RuntimeException("DesiredCapabilities with no browser passed");
if (browserName.equals(BrowserType.CHROME))
caps = new ChromeOptions();
else if (browserName.equals(BrowserType.FIREFOX))
caps = new FirefoxOptions();
else if (browserName.equals(BrowserType.EDGE))
caps = new EdgeOptions();
else
throw new RuntimeException("Currently only Chrome, Firefox and Edge are supported on Testable");
caps.merge(desiredCapabilities);
} else {
caps = capabilities;
}
if (caps instanceof MutableCapabilities) {
if (PROXY_AUTOCONFIG_URL != null && caps instanceof ChromeOptions) {
ChromeOptions opts = (ChromeOptions)caps;
opts.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
opts.addArguments(
"--proxy-pac-url=" + PROXY_AUTOCONFIG_URL,
"--always-authorize-plugins",
"--disable-gpu",
"--no-sandbox",
"--whitelisted-ips",
"--enable-precise-memory-info",
"--ignore-certificate-errors");
if (PROFILE_DIR != null)
opts.addArguments("--user-data-dir=" + PROFILE_DIR);
opts.addArguments("--profile-directory=Profile" + GLOBAL_CLIENT_INDEX);
opts.addArguments("--window-position=0,0");
if(SCALE_FACTOR != null) {
opts.addArguments("--force-device-scale-factor=" + SCALE_FACTOR);
opts.addArguments("--high-dpi-support=" + SCALE_FACTOR);
}
if (DISPLAY_SIZE != null) {
String[] screenSize = DISPLAY_SIZE.split("x", -1);
Float width = Float.parseFloat(screenSize[0]);
Float height = Float.parseFloat(screenSize[1]);
if (SCALE_FACTOR != null) {
width = width / Float.parseFloat(SCALE_FACTOR);
height = height / Float.parseFloat(SCALE_FACTOR);
}
opts.addArguments("--window-size=" + Math.round(width) + "," + Math.round(height));
}
if(USER_AGENT != null){
String[] screenSize = DISPLAY_SIZE.split("x", -1);
Float width = Float.parseFloat(screenSize[0]);
Float height = Float.parseFloat(screenSize[1]);
Map mobileEmulation = new HashMap<>();
Map deviceMetrics = new HashMap<>();
if (SCALE_FACTOR != null) {
width = width / Float.parseFloat(SCALE_FACTOR);
height = height / Float.parseFloat(SCALE_FACTOR);
deviceMetrics.put("width", Math.round(width));
deviceMetrics.put("height", Math.round(height));
deviceMetrics.put("pixelRatio", Float.parseFloat(SCALE_FACTOR));
}
mobileEmulation.put("deviceMetrics", deviceMetrics);
mobileEmulation.put("userAgent", USER_AGENT);
opts.setExperimentalOption("mobileEmulation", mobileEmulation);
}
if (CHROME_BINARY_PATH != null)
opts.setBinary(CHROME_BINARY_PATH);
} else if (caps instanceof FirefoxOptions) {
FirefoxOptions opts = (FirefoxOptions)caps;
opts.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
opts.addPreference("browser.tabs.remote.autostart", false);
opts.addPreference("browser.tabs.remote.autostart.2", false);
opts.addPreference("dom.webnotifications.enabled", false);
opts.addPreference("dom.push.connection.enabled", false);
opts.addPreference("dom.push.enabled", false);
opts.addPreference("dom.push.alwaysConnect", false);
if (PROXY_AUTOCONFIG_URL != null) {
opts.addPreference("network.proxy.type", 2);
opts.addPreference("network.proxy.autoconfig_url", PROXY_AUTOCONFIG_URL);
}
opts.addPreference("browser.startup.page", 0);
opts.addPreference("network.captive-portal-service.enabled", false);
opts.addPreference("browser.newtabpage.activity-stream.disableSnippets", true);
opts.addPreference("browser.newtabpage.activity-stream.feeds.snippets", false);
opts.addPreference("services.sync.prefs.sync.browser.newtabpage.activity-stream.feeds.snippets", false);
if (FIREFOX_BINARY_PATH != null)
opts.setBinary(FIREFOX_BINARY_PATH);
if(USER_AGENT != null){
opts.addPreference("general.useragent.override", USER_AGENT);
opts.addPreference("layout.css.devPixelsPerPx", SCALE_FACTOR);
}
if (DISPLAY_SIZE != null) {
String[] screenSize = DISPLAY_SIZE.split("x", -1);
Float width = Float.parseFloat(screenSize[0]);
Float height = Float.parseFloat(screenSize[1]);
if (SCALE_FACTOR != null) {
width = width / Float.parseFloat(SCALE_FACTOR);
height = height / Float.parseFloat(SCALE_FACTOR);
}
opts.addArguments("--width", "" + Math.round(width));
opts.addArguments("--height", "" + Math.round(height));
}
} else if (PROXY_AUTOCONFIG_URL != null && caps instanceof EdgeOptions) {
EdgeOptions edgeOptions = (EdgeOptions)caps;
edgeOptions.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
List args = new ArrayList<>();
Map experimentalOptions = new HashMap<>();
Map options = new TreeMap<>();
args.add("--proxy-pac-url=" + PROXY_AUTOCONFIG_URL);
args.add("--always-authorize-plugins");
args.add("--disable-gpu");
args.add("--no-sandbox");
args.add("--whitelisted-ips");
args.add("--enable-precise-memory-info");
args.add("--ignore-certificate-errors");
if (PROFILE_DIR != null)
args.add("--user-data-dir=" + PROFILE_DIR);
args.add("--profile-directory=Profile" + GLOBAL_CLIENT_INDEX);
args.add("--window-position=0,0");
if(SCALE_FACTOR != null) {
args.add("--force-device-scale-factor=" + SCALE_FACTOR);
}
if (DISPLAY_SIZE != null) {
String[] screenSize = DISPLAY_SIZE.split("x", -1);
Float width = Float.parseFloat(screenSize[0]);
Float height = Float.parseFloat(screenSize[1]);
if (SCALE_FACTOR != null) {
width = width / Float.parseFloat(SCALE_FACTOR);
height = height / Float.parseFloat(SCALE_FACTOR);
}
args.add("--window-size=" + Math.round(width) + "," + Math.round(height));
}
if(USER_AGENT != null){
String[] screenSize = DISPLAY_SIZE.split("x", -1);
Float width = Float.parseFloat(screenSize[0]);
Float height = Float.parseFloat(screenSize[1]);
Map deviceMetrics = new HashMap<>();
if (SCALE_FACTOR != null) {
width = width / Float.parseFloat(SCALE_FACTOR);
height = height / Float.parseFloat(SCALE_FACTOR);
deviceMetrics.put("width", Math.round(width));
deviceMetrics.put("height", Math.round(height));
deviceMetrics.put("pixelRatio", Float.parseFloat(SCALE_FACTOR));
}
Map mobileEmulation = new HashMap<>();
mobileEmulation.put("deviceMetrics", deviceMetrics);
mobileEmulation.put("userAgent", USER_AGENT);
options.put("mobileEmulation", mobileEmulation);
}
if (EDGE_BINARY_PATH != null)
options.put("binary", EDGE_BINARY_PATH);
options.put("args", ImmutableList.copyOf(args));
edgeOptions.setCapability("ms:edgeOptions", options);
}
}
int port = SELENIUM_PORT > 0 ? SELENIUM_PORT : 4444;
return new RemoteWebDriver(new URL("http://localhost:" + port + "/wd/hub"), caps);
} catch (MalformedURLException e) {
throw new WebDriverException(e);
}
}
/**
* Takes a screenshot of the current browser and copies it to the appropriate output directory to be picked up
* by the test runner (found at the OUTPUT_DIR system property). When run locally the file is not copied but the
* screenshot is still taken.
*
* @param driver The WebDriver instance
* @param name Name of the file to use. Actual file name when run on Testable will be
* [region_name]-[virtual_user]-[test_iteration]-[name] so you have context on which region,
* virtual user, and iteration of the test this image is associated with.
* @return The path where the screenshot can be found.
*/
public static Path takeScreenshot(WebDriver driver, String name) {
try {
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
if (OUTPUT_DIR != null) {
return Files.copy(screenshot.toPath(), Paths.get(OUTPUT_DIR, toName(name)));
} else {
return screenshot.toPath();
}
} catch (IOException e) {
throw new WebDriverException(e);
}
}
/**
* Report a custom metric into the test results. This can be a counter, timing, or histogram. When run locally the
* metric will be output to the console.
*
* Example:
*
*
* {@code TestableSelenium.reportMetric(TestableMetric.newCounterBuilder()
* .withName("My Request Counter")
* .withVal(1)
* .withUnits("requests")
* .build()); }
*
*
* @param metric
*/
public static void reportMetric(TestableMetric metric) {
writeToStream(new Result(metric.getType().name(), metric));
}
/**
* Log a message into the test results at the chosen level. When run outside Testable logging is simply written to
* the console. Trace level logging is only available while smoke testing a scenario. Fatal logging will cause
* your entire test run to stop.
*
* @param level The logging level
* @param msg The message to log.
*/
public static void log(TestableLog.Level level, String msg) {
writeToStream(new Result("Log", new TestableLog(level, msg, System.currentTimeMillis())));
}
/**
* Log an exception into the test results at the chosen level. The entire stack trace will be logged.
* When run outside Testable logging is simply written to the console. Trace level logging is only available while
* smoke testing a scenario. Fatal logging will cause your entire test run to stop.
*
* @param level The logging level
* @param cause The exception to log
*/
public static void log(TestableLog.Level level, Throwable cause) {
String msg = Throwables.getStackTraceAsString(cause);
writeToStream(new Result("Log", new TestableLog(level, msg, System.currentTimeMillis())));
}
/**
* Collect some useful browser performance metrics by executing some Javascript in the browser. Metrics
* include: page load time, speed index, page requests, page weight, time to first byte, time to first paint,
* time to first contentful paint, and time to interactive.
*
* @param driver The WebDriver instance
*/
public static Map collectPerformanceMetrics(WebDriver driver) {
if (RUM_SPEEDINDEXJS != null) {
Map results = (Map) ((JavascriptExecutor) driver).executeScript(RUM_SPEEDINDEXJS);
writeToStream(new Result("BrowserMetrics", results));
return results;
}
return Collections.emptyMap();
}
/**
* Read data from a CSV uploaded to your scenario. {@link TestableCSVReader} for more details of the available API.
* When run locally, the CSV will be loaded from the local classpath.
*
* @param path Path to your CSV file. Relative to the classpath or working directory.
* @return A {@link TestableCSVReader} instance to access the contents of the CSV in various ways.
* @throws IOException
*/
public static TestableCSVReader readCsv(String path) throws IOException {
return new TestableCSVReader(path);
}
/**
* Start a new set of test steps that you want to record and view in the Assertions widget within the Testable
* test results. Allows you to track a series of test steps, whether they pass, any errors that occurred, and the
* duration of each step.
*
* @param name The name of the test.
* @return An object that lets you record test steps and their outcome
*/
public static TestableTest startTest(String name) {
return new TestableTest(name);
}
private static String toName(String name) {
if (REGION_NAME != null) {
return REGION_NAME + "-" + GLOBAL_CLIENT_INDEX + "-" + ITERATION + "-" + name;
} else {
return name;
}
}
static void writeToStream(Result result) {
try {
ObjectMapper mapper = new ObjectMapper();
String text = mapper.writeValueAsString(result);
if (resultStream != null) {
resultStream.println(text);
resultStream.flush();
} else
System.out.println("[" + result.getType() + "] " + text);
} catch (JsonProcessingException jpe) {
throw new RuntimeException(jpe);
}
}
static class Result {
private String type;
private Object data;
public Result(String type, Object data) {
this.type = type;
this.data = data;
}
public String getType() {
return type;
}
public Object getData() {
return data;
}
}
}