diff --git a/pom.xml b/pom.xml
index 31951e2..3b96d87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,12 @@
httpclient
4.5.13
+
+ org.mockito
+ mockito-core
+ 3.12.4
+ test
+
diff --git a/src/main/java/io/percy/selenium/Percy.java b/src/main/java/io/percy/selenium/Percy.java
index 0f1c191..c8e8508 100644
--- a/src/main/java/io/percy/selenium/Percy.java
+++ b/src/main/java/io/percy/selenium/Percy.java
@@ -1,5 +1,6 @@
package io.percy.selenium;
+import org.apache.commons.exec.util.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@@ -14,19 +15,17 @@
import org.json.JSONObject;
import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.HashMap;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
-import org.openqa.selenium.JavascriptExecutor;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebDriverException;
+import org.openqa.selenium.*;
+import org.openqa.selenium.remote.*;
+
+import java.lang.reflect.Field;
/**
* Percy client for visual testing.
@@ -53,6 +52,9 @@ public class Percy {
// Environment information like Java, browser, & SDK versions
private Environment env;
+ // Fetch following properties from capabilities
+ private final List capsNeeded = new ArrayList<>(Arrays.asList("browserName", "platform", "platformName", "version", "osVersion", "proxy"));
+ private final String ignoreElementKey = "ignore_region_selenium_elements";
/**
* @param driver The Selenium WebDriver object that will hold the browser
* session to snapshot.
@@ -164,6 +166,79 @@ public void snapshot(String name, Map options) {
postSnapshot(domSnapshot, name, driver.getCurrentUrl(), options);
}
+ /**
+ * Take a snapshot and upload it to Percy.
+ *
+ * @param name The human-readable name of the screenshot. Should be unique.
+ */
+ public void screenshot(String name) throws UnsupportedOperationException {
+ Map options = new HashMap();
+ screenshot(name, options);
+ }
+
+ /**
+ * Take a snapshot and upload it to Percy.
+ *
+ * @param name The human-readable name of the screenshot. Should be unique.
+ * @param options Extra options
+ */
+ public void screenshot(String name, Map options) throws UnsupportedOperationException {
+ if (!isPercyEnabled) { return; }
+ List driverArray = Arrays.asList(driver.getClass().toString().split("\\$")); // Added to handle testcase (mocked driver)
+ Iterator driverIterator = driverArray.iterator();
+ String driverClass = driverIterator.next();
+ if (!driverClass.equals(RemoteWebDriver.class.toString())) { throw new UnsupportedOperationException(
+ String.format("Driver should be of type RemoteWebDriver, passed is %s", driverClass)
+ ); }
+
+ String sessionId = ((RemoteWebDriver) driver).getSessionId().toString();
+ CommandExecutor executor = ((RemoteWebDriver) driver).getCommandExecutor();
+
+ // Get HttpCommandExecutor From TracedCommandExecutor
+ if (executor instanceof TracedCommandExecutor) {
+ Class className = executor.getClass();
+ try {
+ Field field = className.getDeclaredField("delegate");
+ // make private field accessible
+ field.setAccessible(true);
+ executor = (HttpCommandExecutor)field.get(executor);
+ } catch (Exception e) {
+ log(e.toString());
+ return;
+ }
+ }
+ String remoteWebAddress = ((HttpCommandExecutor) executor).getAddressOfRemoteServer().toString();
+
+ Capabilities caps = ((RemoteWebDriver) driver).getCapabilities();
+ ConcurrentHashMap capabilities = new ConcurrentHashMap();
+
+ Iterator iterator = capsNeeded.iterator();
+ while (iterator.hasNext()) {
+ String cap = iterator.next();
+ if (caps.getCapability(cap) != null) {
+ capabilities.put(cap, caps.getCapability(cap).toString());
+ }
+ }
+
+ if (options.containsKey(ignoreElementKey)) {
+ List ignoreElementIds = getElementIdFromElement((List) options.get(ignoreElementKey));
+ options.remove(ignoreElementKey);
+ options.put("ignore_region_elements", ignoreElementIds);
+ }
+
+ // Build a JSON object to POST back to the agent node process
+ JSONObject json = new JSONObject();
+ json.put("sessionId", sessionId);
+ json.put("commandExecutorUrl", remoteWebAddress);
+ json.put("capabilities", capabilities);
+ json.put("snapshotName", name);
+ json.put("clientInfo", env.getClientInfo());
+ json.put("environmentInfo", env.getEnvironmentInfo());
+ json.put("options", options);
+
+ request("/percy/automateScreenshot", json, name);
+ }
+
/**
* Checks to make sure the local Percy server is running. If not, disable Percy.
*/
@@ -266,17 +341,27 @@ private void postSnapshot(
json.put("clientInfo", env.getClientInfo());
json.put("environmentInfo", env.getEnvironmentInfo());
+ request("/percy/snapshot", json, name);
+ }
+
+ /**
+ * POST data to the Percy Agent node process.
+ *
+ * @param url Endpoint to be called.
+ * @param name The human-readable name of the snapshot. Should be unique.
+ * @param json Json object of all properties.
+ */
+ protected void request(String url, JSONObject json, String name) {
StringEntity entity = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- HttpPost request = new HttpPost(PERCY_SERVER_ADDRESS + "/percy/snapshot");
+ HttpPost request = new HttpPost(PERCY_SERVER_ADDRESS + url);
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);
} catch (Exception ex) {
if (PERCY_DEBUG) { log(ex.toString()); }
log("Could not post snapshot " + name);
}
-
}
/**
@@ -291,6 +376,15 @@ private String buildSnapshotJS(Map options) {
return jsBuilder.toString();
}
+ private List getElementIdFromElement(List elements) {
+ List ignoredElementsArray = new ArrayList<>();
+ for (int index = 0; index < elements.size(); index++) {
+ String elementId = elements.get(index).getId();
+ ignoredElementsArray.add(elementId);
+ }
+ return ignoredElementsArray;
+ }
+
private void log(String message) {
System.out.println(LABEL + " " + message);
}
diff --git a/src/test/java/io/percy/selenium/SdkTest.java b/src/test/java/io/percy/selenium/SdkTest.java
index 4a8499e..e36d25c 100644
--- a/src/test/java/io/percy/selenium/SdkTest.java
+++ b/src/test/java/io/percy/selenium/SdkTest.java
@@ -9,6 +9,8 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
@@ -19,7 +21,10 @@
import io.github.bonigarcia.wdm.WebDriverManager;
-public class SdkTest {
+import org.openqa.selenium.remote.*;
+import static org.mockito.Mockito.*;
+import java.net.URL;
+ public class SdkTest {
private static final String TEST_URL = "http://localhost:8000";
private static WebDriver driver;
private static Percy percy;
@@ -108,4 +113,50 @@ public void snapshotWithOptions() {
options.put("widths", Arrays.asList(768, 992, 1200));
percy.snapshot("Site with options", options);
}
+
+ @Test
+ public void takeScreenshotWhenNonRemoteWebDriver() {
+ assertThrows(UnsupportedOperationException.class, () -> {
+ percy.screenshot("Test");
+ });
+ }
+ @Test
+ public void takeScreenshot() {
+ RemoteWebDriver mockedDriver = mock(RemoteWebDriver.class);
+ HttpCommandExecutor commandExecutor = mock(HttpCommandExecutor.class);
+ try {
+ when(commandExecutor.getAddressOfRemoteServer()).thenReturn(new URL("https://hub-cloud.browserstack.com/wd/hub"));
+ } catch (Exception e) {
+ }
+ percy = spy(new Percy(mockedDriver));
+ when(mockedDriver.getSessionId()).thenReturn(new SessionId("123"));
+ when(mockedDriver.getCommandExecutor()).thenReturn(commandExecutor);
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ capabilities.setCapability("browserName", "Chrome");
+ when(mockedDriver.getCapabilities()).thenReturn(capabilities);
+ percy.screenshot("Test");
+ verify(percy).request(eq("/percy/automateScreenshot"), any(), eq("Test"));
+ }
+
+ @Test
+ public void takeScreenshotWithOptions() {
+ RemoteWebDriver mockedDriver = mock(RemoteWebDriver.class);
+ HttpCommandExecutor commandExecutor = mock(HttpCommandExecutor.class);
+ try {
+ when(commandExecutor.getAddressOfRemoteServer()).thenReturn(new URL("https://hub-cloud.browserstack.com/wd/hub"));
+ } catch (Exception e) {
+ }
+ percy = spy(new Percy(mockedDriver));
+ when(mockedDriver.getSessionId()).thenReturn(new SessionId("123"));
+ when(mockedDriver.getCommandExecutor()).thenReturn(commandExecutor);
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ capabilities.setCapability("browserName", "Chrome");
+ when(mockedDriver.getCapabilities()).thenReturn(capabilities);
+ Map options = new HashMap();
+ RemoteWebElement mockedElement = mock(RemoteWebElement.class);
+ when(mockedElement.getId()).thenReturn("1234");
+ options.put("ignore_region_selenium_elements", Arrays.asList(mockedElement));
+ percy.screenshot("Test", options);
+ verify(percy).request(eq("/percy/automateScreenshot"), any() , eq("Test"));
+ }
}