Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add PoA Support #216

Merged
merged 22 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2c6b7a1
POA changes
chinmay-browserstack May 5, 2023
c5e67ef
Remove extra print
chinmay-browserstack May 5, 2023
ab862b7
Undo healthcheck changes
chinmay-browserstack May 5, 2023
de35e94
added tests and options in param
chinmay-browserstack May 8, 2023
238828d
add mockito dependency
chinmay-browserstack May 8, 2023
b68dcd8
compatable version with java 8
chinmay-browserstack May 8, 2023
bac8a79
resolve comment
chinmay-browserstack May 8, 2023
1f4eb7b
fix concurrent error in test
chinmay-browserstack May 8, 2023
9b06dac
fix concurrent access issue
chinmay-browserstack May 8, 2023
0a1cc55
Fix null pointer in test
chinmay-browserstack May 8, 2023
0f79851
changed according to comments
chinmay-browserstack May 10, 2023
c2e0345
add detail for debug failed testcase
chinmay-browserstack May 10, 2023
88f0abc
update mockito to fix testcases
chinmay-browserstack May 10, 2023
672c35d
fix for test cases
chinmay-browserstack May 10, 2023
e463545
fix concurrent access 1
chinmay-browserstack May 10, 2023
c8cf55b
Addresed comments
chinmay-browserstack May 29, 2023
3b78466
POA Support for ignoreRegion (#220)
chinmay-browserstack Jun 19, 2023
752a877
Ignore Element key name change (#221)
chinmay-browserstack Jun 21, 2023
07d3f64
Fixing java sdk for missing property (#226)
Amit3200 Jul 3, 2023
49b85c8
CI Add validation for branch input (#215)
samarsault May 10, 2023
a5ff68f
Adding Code Scanner Semgrep.yml workflow file
bstack-security-github Jun 22, 2023
7f1df6e
Merge branch 'master' into PPLT_2047
Amit3200 Jul 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
chinmay-browserstack marked this conversation as resolved.
Show resolved Hide resolved
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());
}
}
chinmay-browserstack marked this conversation as resolved.
Show resolved Hide resolved

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() {
chinmay-browserstack marked this conversation as resolved.
Show resolved Hide resolved
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);
chinmay-browserstack marked this conversation as resolved.
Show resolved Hide resolved
verify(percy).request(eq("/percy/automateScreenshot"), any() , eq("Test"));
}
}