Skip to content

Commit

Permalink
✨ Add PoA Support (#216)
Browse files Browse the repository at this point in the history
* POA changes

* Remove extra print

* Undo healthcheck changes

* added tests and options in param

* add mockito dependency

* compatable version with java 8

* resolve comment

* fix concurrent error in test

* fix concurrent access issue

* Fix null pointer in test

* changed according to comments

* add detail for debug failed testcase

* update mockito to fix testcases

* fix for test cases

* fix concurrent access 1

* Addresed comments

* POA Support for ignoreRegion (#220)

* Added logic for ignore region

* added test case

* Added validation in tests

* Possible concurrent thread fix 1

* Possible concurrent thread fix 2

* Possible concurrent thread fix 3

* Possible concurrent thread fix 4

* Possible concurrent thread fix 5

* Ignore Element key name change (#221)

* Added logic for ignore region

* added test case

* Added validation in tests

* Possible concurrent thread fix 1

* Possible concurrent thread fix 2

* Possible concurrent thread fix 3

* Possible concurrent thread fix 4

* Possible concurrent thread fix 5

* Fix ignore region logic

* Fixing java sdk for missing property (#226)

* CI Add validation for branch input (#215)

* Adding Code Scanner Semgrep.yml workflow file

---------

Co-authored-by: Amit Singh Sansoya <tusharamit@yahoo.com>
Co-authored-by: Samarjeet <samarsault@gmail.com>
Co-authored-by: bstack-security-github <116066275+bstack-security-github@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 3, 2023
1 parent df1f731 commit a211749
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 11 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
114 changes: 104 additions & 10 deletions src/main/java/io/percy/selenium/Percy.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand All @@ -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<String> 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.
Expand Down Expand Up @@ -164,6 +166,79 @@ public void snapshot(String name, Map<String, Object> 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<String, Object> options = new HashMap<String, Object>();
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<String, Object> options) throws UnsupportedOperationException {
if (!isPercyEnabled) { return; }
List<String> driverArray = Arrays.asList(driver.getClass().toString().split("\\$")); // Added to handle testcase (mocked driver)
Iterator<String> 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<String, String> capabilities = new ConcurrentHashMap<String, String>();

Iterator<String> 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<String> ignoreElementIds = getElementIdFromElement((List<RemoteWebElement>) 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.
*/
Expand Down Expand Up @@ -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);
}

}

/**
Expand All @@ -291,6 +376,15 @@ private String buildSnapshotJS(Map<String, Object> options) {
return jsBuilder.toString();
}

private List<String> getElementIdFromElement(List<RemoteWebElement> elements) {
List<String> 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);
}
Expand Down
53 changes: 52 additions & 1 deletion src/test/java/io/percy/selenium/SdkTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String, Object> options = new HashMap<String, Object>();
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"));
}
}

0 comments on commit a211749

Please sign in to comment.