-
Notifications
You must be signed in to change notification settings - Fork 241
Description
Version
1.52.0
Steps to reproduce
Use this reproduction example, run the main method.
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import java.util.List;
public class LocatorScreenshotBugReproducer {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.playwright.dev");
var heading = page.locator("h1");
heading.waitFor();
// this throws exception, only if a mask is specified
heading.screenshot(
new Locator.ScreenshotOptions()
.setMask(List.of(heading.locator("span"))));
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
Expected behavior
Screenshot should be taken
Actual behavior
The code fails with an exception and the following stack trace:
com.google.gson.JsonIOException: Failed making field 'java.lang.Throwable#detailMessage' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:38)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:286)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.MapTypeAdapterFactory.create(MapTypeAdapterFactory.java:125)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
at com.google.gson.Gson.getAdapter(Gson.java:556)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:55)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:70)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:196)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:368)
at com.google.gson.Gson.toJson(Gson.java:842)
at com.google.gson.Gson.toJsonTree(Gson.java:712)
at com.google.gson.Gson.toJsonTree(Gson.java:689)
at com.microsoft.playwright.impl.ElementHandleImpl.screenshotImpl(ElementHandleImpl.java:349)
at com.microsoft.playwright.impl.ElementHandleImpl.lambda$screenshot$25(ElementHandleImpl.java:329)
at com.microsoft.playwright.impl.LoggingSupport.withLogging(LoggingSupport.java:47)
at com.microsoft.playwright.impl.ChannelOwner.withLogging(ChannelOwner.java:97)
at com.microsoft.playwright.impl.ElementHandleImpl.screenshot(ElementHandleImpl.java:329)
at com.microsoft.playwright.impl.LocatorImpl.lambda$screenshot$5(LocatorImpl.java:486)
at com.microsoft.playwright.impl.LocatorImpl.withElement(LocatorImpl.java:85)
at com.microsoft.playwright.impl.LocatorImpl.screenshot(LocatorImpl.java:486)
at LocatorScreenshotBugReproducer.main(LocatorScreenshotBugReproducer.java:22)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.Throwable.detailMessage accessible: module java.base does not "opens java.lang" to unnamed module @59fa0ced
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:35)
... 47 more
Additional context
Bug analysis:
The Locator screenshot functionality forgets to remove the mask
field from the generic Gson serialization of the options, and Gson finally fails because deep inside the mask
field (which is a Locator
) is an Exception
field which cannot be serialized.
The Page screenshot code has handled this by special-casing the mask
field. This needs to be done also for the Locator code (actually located in ElementHandleImpl
).
Working screenshot code:
playwright-java/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
Lines 1211 to 1222 in fddb146
List<Locator> 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); | |
} |
Special handling for mask
missing:
playwright-java/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java
Line 332 in fddb146
private byte[] screenshotImpl(ScreenshotOptions options) { |
Environment
Java 17