From 85011e9c6712d04a1506d402efda84f2135fc88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20Hy=C3=B6tyl=C3=A4?= Date: Thu, 24 Jul 2025 11:21:56 +0200 Subject: [PATCH] fix: Fix masking in single element screenshots. Using masks in single element screenshots (`Locator.screenshot()`) never worked, because the mask option failed to be serialized. When a mask is provided as an option, serialize it manually. This is the same implementation as in `PageImpl.screenshot()`. Fixes #1790 --- .../playwright/impl/ElementHandleImpl.java | 19 ++++- .../playwright/TestPageScreenshot.java | 79 +++++++++++-------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java index a8b6667bc..64d3b3417 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -21,6 +21,7 @@ import com.google.gson.JsonObject; import com.microsoft.playwright.ElementHandle; import com.microsoft.playwright.Frame; +import com.microsoft.playwright.Locator; import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.options.BoundingBox; import com.microsoft.playwright.options.ElementState; @@ -44,7 +45,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle { ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); - this.frame = (FrameImpl)parent; + this.frame = (FrameImpl) parent; } @Override @@ -282,8 +283,18 @@ public byte[] screenshot(ScreenshotOptions options) { } } } + List mask = options.mask; + options.mask = null; JsonObject params = gson().toJsonTree(options).getAsJsonObject(); + options.mask = mask; params.remove("path"); + if (mask != null) { + JsonArray maskArray = new JsonArray(); + for (Locator locator : mask) { + maskArray.add(((LocatorImpl) locator).toProtocol()); + } + params.add("mask", maskArray); + } JsonObject json = sendMessage("screenshot", params, frame.timeout(options.timeout)).getAsJsonObject(); byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString()); @@ -304,13 +315,13 @@ public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) { @Override public List selectOption(String value, SelectOptionOptions options) { - String[] values = value == null ? null : new String[]{ value }; + String[] values = value == null ? null : new String[]{value}; return selectOption(values, options); } @Override public List selectOption(ElementHandle value, SelectOptionOptions options) { - ElementHandle[] values = value == null ? null : new ElementHandle[]{ value }; + ElementHandle[] values = value == null ? null : new ElementHandle[]{value}; return selectOption(values, options); } @@ -328,7 +339,7 @@ public List selectOption(String[] values, SelectOptionOptions options) { @Override public List selectOption(SelectOption value, SelectOptionOptions options) { - SelectOption[] values = value == null ? null : new SelectOption[]{ value }; + SelectOption[] values = value == null ? null : new SelectOption[]{value}; return selectOption(values, options); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java index a756476b3..aad72edd0 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java @@ -69,8 +69,8 @@ static private void rafraf(Page page) { // Do a double raf since single raf does not // actually guarantee a new animation frame. page.evaluate("() => new Promise(x => {\n" + - " requestAnimationFrame(() => requestAnimationFrame(x));\n" + - " })"); + " requestAnimationFrame(() => requestAnimationFrame(x));\n" + + " })"); } @Test @@ -136,7 +136,7 @@ void shouldNotCaptureInfiniteWebAnimations() { } @Test - void maskShouldWork() { + void maskShouldWorkForPage() { page.setViewportSize(500, 500); page.navigate(server.PREFIX + "/grid.html"); byte[] screenshot = page.screenshot(new Page.ScreenshotOptions() @@ -146,6 +146,17 @@ void maskShouldWork() { assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot)); } + @Test + void maskShouldWorkForLocator() { + page.navigate(server.PREFIX + "/grid.html"); + Locator locatorToScreenshot = page.locator("div").first(); + byte[] screenshot = locatorToScreenshot.screenshot(new Locator.ScreenshotOptions() + .setMask(asList(page.locator("img")))); + // TODO: toMatchSnapshot is not present in java, so we only checks that masked screenshot is different. + byte[] originalScreenshot = locatorToScreenshot.screenshot(); + assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot)); + } + @Test void shouldWorkWithDeviceScaleFactorAndClip() { try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() @@ -186,16 +197,16 @@ void shouldWorkWithDeviceScaleFactorAndScaleDevice() { @Test void shouldNotCaptureBlinkingCaretByDefault() { page.setContent("\n" + - " \n" + - " \n" + - " \n" + - "
\n"); + " stylesheet rules will throw.\n" + + " -->\n" + + " \n" + + " \n" + + " \n" + + "
\n"); Locator div = page.locator("div"); div.type("foo bar"); byte[] screenshot = div.screenshot(); @@ -210,19 +221,19 @@ void shouldNotCaptureBlinkingCaretByDefault() { } @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="fixme") + @DisabledIf(value = "com.microsoft.playwright.TestBase#isFirefox", disabledReason = "fixme") void shouldCaptureBlinkingCaretIfExplicitlyAskedFor() { page.setContent(" \n" + - " \n" + - " \n" + - " \n" + - "
\n"); + " stylesheet rules will throw.\n" + + " -->\n" + + " \n" + + " \n" + + " \n" + + "
\n"); Locator div = page.locator("div"); div.type("foo bar"); byte[] screenshot = div.screenshot(); @@ -265,32 +276,32 @@ static boolean isScreenshotTestDisabled() { } @Test - @DisabledIf(value="com.microsoft.playwright.TestPageScreenshot#isScreenshotTestDisabled", disabledReason="array lengths differ") + @DisabledIf(value = "com.microsoft.playwright.TestPageScreenshot#isScreenshotTestDisabled", disabledReason = "array lengths differ") void shouldHideElementsBasedOnAttr() throws IOException { page.setViewportSize(500, 500); page.navigate(server.PREFIX + "/grid.html"); page.locator("div").nth(5).evaluate("element => {\n" + - " element.setAttribute('data-test-screenshot', 'hide');\n" + - "}"); + " element.setAttribute('data-test-screenshot', 'hide');\n" + + "}"); byte[] actual = page.screenshot(new Page.ScreenshotOptions().setStyle("[data-test-screenshot=\"hide\"] {\n" + - " visibility: hidden;\n" + - " }")); + " visibility: hidden;\n" + + " }")); assertArrayEquals(expectedScreenshot("hide-should-work"), actual, "Screenshots should match"); Object visibility = page.locator("div").nth(5).evaluate("element => element.style.visibility"); assertEquals("", visibility); } @Test - @DisabledIf(value="com.microsoft.playwright.TestPageScreenshot#isScreenshotTestDisabled", disabledReason="array lengths differ") + @DisabledIf(value = "com.microsoft.playwright.TestPageScreenshot#isScreenshotTestDisabled", disabledReason = "array lengths differ") void shouldRemoveElementsBasedOnAttr() throws IOException { page.setViewportSize(500, 500); page.navigate(server.PREFIX + "/grid.html"); page.locator("div").nth(5).evaluate("element => {\n" + - " element.setAttribute('data-test-screenshot', 'remove');\n" + - " }"); + " element.setAttribute('data-test-screenshot', 'remove');\n" + + " }"); byte[] actual = page.screenshot(new Page.ScreenshotOptions().setStyle("[data-test-screenshot=\"remove\"] {\n" + - " display: none;\n" + - " }")); + " display: none;\n" + + " }")); assertArrayEquals(expectedScreenshot("remove-should-work"), actual, "Screenshots should match"); Object display = page.locator("div").nth(5).evaluate("element => element.style.display"); assertEquals("", display);