Skip to content

Commit 6aacb8a

Browse files
resolving comments
1 parent 861e6e4 commit 6aacb8a

2 files changed

Lines changed: 205 additions & 10 deletions

File tree

src/main/java/io/percy/selenium/Percy.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public class Percy {
5050

5151
private static String RESPONSIVE_CAPTURE_SLEEP_TIME = System.getenv().getOrDefault("RESPONSIVE_CAPTURE_SLEEP_TIME", "");
5252

53-
private static String PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE = System.getenv().getOrDefault("PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE", "false").toLowerCase();
53+
private static boolean PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE = Boolean.parseBoolean(System.getenv().getOrDefault("PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE", "false").toLowerCase());
5454

55-
private static boolean PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT = Boolean.parseBoolean(System.getenv().getOrDefault("PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT", "false"));
55+
private static boolean PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT = Boolean.parseBoolean(System.getenv().getOrDefault("PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT", "false").toLowerCase());
5656
private static final int WIDTHS_CONFIG_TIMEOUT_MS = 30000;
5757
// for logging
5858
private static String LABEL = "[\u001b[35m" + (PERCY_DEBUG ? "percy:java" : "percy") + "\u001b[39m]";
@@ -619,8 +619,6 @@ private Map<String, Object> processFrame(WebElement frameElement, Map<String, Ob
619619
String frameUrl = frameElement.getAttribute("src");
620620
if (frameUrl == null) frameUrl = "unknown-src";
621621
final String finalFrameUrl = frameUrl;
622-
log("processFrame: checking iframe src=\"" + finalFrameUrl + "\"", "debug");
623-
624622
String percyElementId = frameElement.getAttribute("data-percy-element-id");
625623
log("processFrame: data-percy-element-id=\"" + percyElementId + "\" for src=\"" + finalFrameUrl + "\"", "debug");
626624
if (percyElementId == null || percyElementId.isEmpty()) {
@@ -665,12 +663,9 @@ private Map<String, Object> processFrame(WebElement frameElement, Map<String, Ob
665663
}
666664

667665
private Map<String, Object> getSerializedDOM(JavascriptExecutor jse, Set<Cookie> cookies, Map<String, Object> options) {
668-
// 1. Serialize the main page first (this adds the data-percy-element-ids)
669666
Map<String, Object> domSnapshot = (Map<String, Object>) jse.executeScript(buildSnapshotJS(options));
670667
Map<String, Object> mutableSnapshot = new HashMap<>(domSnapshot);
671668
mutableSnapshot.put("cookies", cookies);
672-
673-
// 2. Process CORS IFrames
674669
try {
675670
String pageOrigin = getOrigin(driver.getCurrentUrl());
676671
List<WebElement> iframes = driver.findElements(By.tagName("iframe"));
@@ -859,6 +854,7 @@ public List<Map<String, Object>> captureResponsiveDom(WebDriver driver, Set<Cook
859854
int currentWidth = windowSize.getWidth();
860855
int currentHeight = windowSize.getHeight();
861856
int lastWindowWidth = currentWidth;
857+
int lastWindowHeight = currentHeight;
862858
int resizeCount = 0;
863859
JavascriptExecutor jse = (JavascriptExecutor) driver;
864860
jse.executeScript("PercyDOM.waitForResize()");
@@ -871,12 +867,13 @@ public List<Map<String, Object>> captureResponsiveDom(WebDriver driver, Set<Cook
871867
int width = ((Number) widthObj).intValue();
872868
Object heightObj = widthMap.get("height");
873869
int heightForWidth = (heightObj instanceof Number)? ((Number) heightObj).intValue(): targetHeight;
874-
if (lastWindowWidth != width) {
870+
if (lastWindowWidth != width || lastWindowHeight != heightForWidth) {
875871
resizeCount++;
876872
changeWindowDimensionAndWait(driver, width, heightForWidth, resizeCount);
877873
lastWindowWidth = width;
874+
lastWindowHeight = heightForWidth;
878875
}
879-
if ("true".equals(PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE)) {
876+
if (PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE) {
880877
driver.navigate().refresh();
881878
jse.executeScript(fetchPercyDOM());
882879
jse.executeScript("PercyDOM.waitForResize()");
@@ -896,7 +893,7 @@ public List<Map<String, Object>> captureResponsiveDom(WebDriver driver, Set<Cook
896893
changeWindowDimensionAndWait(driver, currentWidth, currentHeight, resizeCount + 1);
897894

898895
return domSnapshots;
899-
}
896+
}
900897

901898
protected static void log(String message) {
902899
log(message, "info");

src/test/java/io/percy/selenium/SdkTest.java

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.List;
1515
import java.util.Set;
1616
import java.util.Map;
17+
import java.util.concurrent.atomic.AtomicInteger;
1718
import java.util.concurrent.atomic.AtomicReference;
1819

1920
import org.junit.jupiter.api.AfterAll;
@@ -32,6 +33,7 @@
3233
import org.json.JSONObject;
3334
import org.openqa.selenium.By;
3435
import org.openqa.selenium.Cookie;
36+
import org.openqa.selenium.Dimension;
3537
import org.openqa.selenium.JavascriptExecutor;
3638
import org.openqa.selenium.Keys;
3739
import org.openqa.selenium.WebDriver;
@@ -938,6 +940,202 @@ public void createRegionTest() {
938940
assertEquals(0.1, assertion.get("diffIgnoreThreshold"));
939941
}
940942

943+
@Test
944+
public void captureResponsiveDomResizesToCorrectWidthAndHeight() throws Exception {
945+
// Serve two widths: one with an explicit height and one without.
946+
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
947+
server.createContext("/percy/widths-config", exchange -> {
948+
byte[] body = "{\"widths\":[{\"width\":375,\"height\":812},{\"width\":1280}]}".getBytes("UTF-8");
949+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, body.length);
950+
try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
951+
});
952+
server.start();
953+
954+
String originalAddress = getStaticStringField(Percy.class, "PERCY_SERVER_ADDRESS");
955+
try {
956+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", "http://localhost:" + server.getAddress().getPort());
957+
958+
RemoteWebDriver mockedDriver = mock(RemoteWebDriver.class);
959+
Percy mockedPercy = spy(new Percy(mockedDriver));
960+
setField(mockedPercy, "domJs", "/* percy dom */");
961+
setField(mockedPercy, "cliConfig", new JSONObject().put("snapshot", new JSONObject()));
962+
963+
// Track actual setSize calls so window.resizeCount can echo the right value.
964+
AtomicInteger dimensionChangeCount = new AtomicInteger(0);
965+
WebDriver.Options driverOptions = mock(WebDriver.Options.class);
966+
WebDriver.Window driverWindow = mock(WebDriver.Window.class);
967+
when(mockedDriver.manage()).thenReturn(driverOptions);
968+
when(driverOptions.window()).thenReturn(driverWindow);
969+
when(driverWindow.getSize()).thenReturn(new Dimension(1024, 768));
970+
doAnswer(inv -> { dimensionChangeCount.incrementAndGet(); return null; })
971+
.when(driverWindow).setSize(any(Dimension.class));
972+
973+
when(mockedDriver.getCurrentUrl()).thenReturn("https://example.com");
974+
when(mockedDriver.findElements(By.tagName("iframe"))).thenReturn(Collections.emptyList());
975+
when(((JavascriptExecutor) mockedDriver).executeScript(any(String.class))).thenAnswer(invocation -> {
976+
String script = invocation.getArgument(0);
977+
if (script.equals("return window.resizeCount")) {
978+
return (long) dimensionChangeCount.get();
979+
}
980+
if (script.startsWith("return PercyDOM.serialize(")) {
981+
Map<String, Object> snap = new HashMap<>();
982+
snap.put("dom", "test");
983+
return snap;
984+
}
985+
return null;
986+
});
987+
988+
Map<String, Object> options = new HashMap<>();
989+
options.put("widths", Arrays.asList(375, 1280));
990+
List<Map<String, Object>> snapshots =
991+
mockedPercy.captureResponsiveDom(mockedDriver, new HashSet<>(), options);
992+
993+
ArgumentCaptor<Dimension> sizeCaptor = ArgumentCaptor.forClass(Dimension.class);
994+
// 3 calls expected: resize to 375x812, resize to 1280x768, restore 1024x768.
995+
verify(driverWindow, times(3)).setSize(sizeCaptor.capture());
996+
List<Dimension> sizes = sizeCaptor.getAllValues();
997+
998+
// First width uses the explicit height returned by the server.
999+
assertEquals(375, sizes.get(0).getWidth());
1000+
assertEquals(812, sizes.get(0).getHeight());
1001+
1002+
// Second width has no explicit height: falls back to currentHeight (768).
1003+
assertEquals(1280, sizes.get(1).getWidth());
1004+
assertEquals(768, sizes.get(1).getHeight());
1005+
1006+
// Final call restores the original window size.
1007+
assertEquals(1024, sizes.get(2).getWidth());
1008+
assertEquals(768, sizes.get(2).getHeight());
1009+
1010+
// Each snapshot must carry the width it was captured at.
1011+
assertEquals(2, snapshots.size());
1012+
assertEquals(375, snapshots.get(0).get("width"));
1013+
assertEquals(1280, snapshots.get(1).get("width"));
1014+
} finally {
1015+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", originalAddress);
1016+
server.stop(0);
1017+
}
1018+
}
1019+
1020+
@Test
1021+
public void captureResponsiveDomSkipsResizeWhenDimensionsUnchanged() throws Exception {
1022+
// Return the exact same width as the initial window — no per-width resize should occur.
1023+
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
1024+
server.createContext("/percy/widths-config", exchange -> {
1025+
byte[] body = "{\"widths\":[{\"width\":1024}]}".getBytes("UTF-8");
1026+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, body.length);
1027+
try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
1028+
});
1029+
server.start();
1030+
1031+
String originalAddress = getStaticStringField(Percy.class, "PERCY_SERVER_ADDRESS");
1032+
try {
1033+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", "http://localhost:" + server.getAddress().getPort());
1034+
1035+
RemoteWebDriver mockedDriver = mock(RemoteWebDriver.class);
1036+
Percy mockedPercy = spy(new Percy(mockedDriver));
1037+
setField(mockedPercy, "domJs", "/* percy dom */");
1038+
setField(mockedPercy, "cliConfig", new JSONObject().put("snapshot", new JSONObject()));
1039+
1040+
AtomicInteger dimensionChangeCount = new AtomicInteger(0);
1041+
WebDriver.Options driverOptions = mock(WebDriver.Options.class);
1042+
WebDriver.Window driverWindow = mock(WebDriver.Window.class);
1043+
when(mockedDriver.manage()).thenReturn(driverOptions);
1044+
when(driverOptions.window()).thenReturn(driverWindow);
1045+
when(driverWindow.getSize()).thenReturn(new Dimension(1024, 768));
1046+
doAnswer(inv -> { dimensionChangeCount.incrementAndGet(); return null; })
1047+
.when(driverWindow).setSize(any(Dimension.class));
1048+
1049+
when(mockedDriver.getCurrentUrl()).thenReturn("https://example.com");
1050+
when(mockedDriver.findElements(By.tagName("iframe"))).thenReturn(Collections.emptyList());
1051+
when(((JavascriptExecutor) mockedDriver).executeScript(any(String.class))).thenAnswer(invocation -> {
1052+
String script = invocation.getArgument(0);
1053+
if (script.equals("return window.resizeCount")) {
1054+
return (long) dimensionChangeCount.get();
1055+
}
1056+
if (script.startsWith("return PercyDOM.serialize(")) {
1057+
Map<String, Object> snap = new HashMap<>();
1058+
snap.put("dom", "test");
1059+
return snap;
1060+
}
1061+
return null;
1062+
});
1063+
1064+
Map<String, Object> options = new HashMap<>();
1065+
options.put("widths", Arrays.asList(1024));
1066+
mockedPercy.captureResponsiveDom(mockedDriver, new HashSet<>(), options);
1067+
1068+
// Only the final restore call should fire; no per-width resize.
1069+
ArgumentCaptor<Dimension> sizeCaptor = ArgumentCaptor.forClass(Dimension.class);
1070+
verify(driverWindow, times(1)).setSize(sizeCaptor.capture());
1071+
Dimension restoreSize = sizeCaptor.getValue();
1072+
assertEquals(1024, restoreSize.getWidth());
1073+
assertEquals(768, restoreSize.getHeight());
1074+
} finally {
1075+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", originalAddress);
1076+
server.stop(0);
1077+
}
1078+
}
1079+
1080+
@Test
1081+
public void captureResponsiveDomRefreshesDriverForEachWidthWhenReloadFlagSet() throws Exception {
1082+
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
1083+
server.createContext("/percy/widths-config", exchange -> {
1084+
byte[] body = "{\"widths\":[{\"width\":375},{\"width\":1280}]}".getBytes("UTF-8");
1085+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, body.length);
1086+
try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
1087+
});
1088+
server.start();
1089+
1090+
String originalAddress = getStaticStringField(Percy.class, "PERCY_SERVER_ADDRESS");
1091+
boolean originalReloadFlag = getStaticBooleanField(Percy.class, "PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE");
1092+
try {
1093+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", "http://localhost:" + server.getAddress().getPort());
1094+
setStaticField(Percy.class, "PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE", true);
1095+
1096+
RemoteWebDriver mockedDriver = mock(RemoteWebDriver.class);
1097+
Percy mockedPercy = spy(new Percy(mockedDriver));
1098+
setField(mockedPercy, "domJs", "/* percy dom */");
1099+
setField(mockedPercy, "cliConfig", new JSONObject().put("snapshot", new JSONObject()));
1100+
1101+
WebDriver.Options driverOptions = mock(WebDriver.Options.class);
1102+
WebDriver.Window driverWindow = mock(WebDriver.Window.class);
1103+
WebDriver.Navigation navigation = mock(WebDriver.Navigation.class);
1104+
when(mockedDriver.manage()).thenReturn(driverOptions);
1105+
when(driverOptions.window()).thenReturn(driverWindow);
1106+
when(driverWindow.getSize()).thenReturn(new Dimension(1024, 768));
1107+
when(mockedDriver.navigate()).thenReturn(navigation);
1108+
when(mockedDriver.getCurrentUrl()).thenReturn("https://example.com");
1109+
when(mockedDriver.findElements(By.tagName("iframe"))).thenReturn(Collections.emptyList());
1110+
1111+
// After each reload resizeCount resets to 0, so the next changeWindowDimensionAndWait
1112+
// call uses resizeCount=1. Return 1L so the wait resolves without timing out.
1113+
when(((JavascriptExecutor) mockedDriver).executeScript(any(String.class))).thenAnswer(invocation -> {
1114+
String script = invocation.getArgument(0);
1115+
if (script.equals("return window.resizeCount")) {
1116+
return 1L;
1117+
}
1118+
if (script.startsWith("return PercyDOM.serialize(")) {
1119+
Map<String, Object> snap = new HashMap<>();
1120+
snap.put("dom", "test");
1121+
return snap;
1122+
}
1123+
return null;
1124+
});
1125+
1126+
Map<String, Object> options = new HashMap<>();
1127+
options.put("widths", Arrays.asList(375, 1280));
1128+
mockedPercy.captureResponsiveDom(mockedDriver, new HashSet<>(), options);
1129+
1130+
// driver.navigate().refresh() must be called once per captured width.
1131+
verify(navigation, times(2)).refresh();
1132+
} finally {
1133+
setStaticField(Percy.class, "PERCY_SERVER_ADDRESS", originalAddress);
1134+
setStaticField(Percy.class, "PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE", originalReloadFlag);
1135+
server.stop(0);
1136+
}
1137+
}
1138+
9411139
private static Object invokePrivate(Object target, String methodName, Class<?>[] paramTypes, Object... args)
9421140
throws Exception {
9431141
Method method = Percy.class.getDeclaredMethod(methodName, paramTypes);

0 commit comments

Comments
 (0)