Skip to content

Commit

Permalink
feat: roll 1.42.0-alpha driver, implement page.addLocatorHandler (#1494)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s authored Feb 15, 2024
1 parent 99ab899 commit 7f2a5fe
Show file tree
Hide file tree
Showing 18 changed files with 439 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->121.0.6167.57<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->122.0.6261.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->121.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->122.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public interface BrowserContext extends AutoCloseable {

/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler
* argument.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1779,8 +1779,8 @@ default String inputValue() {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
Expand Down Expand Up @@ -1809,8 +1809,8 @@ default void press(String key) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
Expand Down
8 changes: 4 additions & 4 deletions playwright/src/main/java/com/microsoft/playwright/Frame.java
Original file line number Diff line number Diff line change
Expand Up @@ -3991,8 +3991,8 @@ default Locator locator(String selector) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
Expand Down Expand Up @@ -4020,8 +4020,8 @@ default void press(String selector, String key) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ public TypeOptions setDelay(double delay) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
Expand Down Expand Up @@ -197,8 +197,8 @@ default void press(String key) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4084,8 +4084,8 @@ default Locator locator(Locator selectorOrLocator) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
Expand Down Expand Up @@ -4123,8 +4123,8 @@ default void press(String key) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
Expand Down
81 changes: 76 additions & 5 deletions playwright/src/main/java/com/microsoft/playwright/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public interface Page extends AutoCloseable {

/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
*
Expand Down Expand Up @@ -5804,8 +5804,8 @@ default byte[] pdf() {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
Expand Down Expand Up @@ -5847,8 +5847,8 @@ default void press(String selector, String key) {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
Expand Down Expand Up @@ -5895,6 +5895,77 @@ default ElementHandle querySelector(String selector) {
* @since v1.9
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* Sometimes, the web page can show an overlay that obstructs elements behind it and prevents certain actions, like click,
* from completing. When such an overlay is shown predictably, we recommend dismissing it as a part of your test flow.
* However, sometimes such an overlay may appear non-deterministically, for example certain cookies consent dialogs behave
* this way. In this case, {@link Page#addLocatorHandler Page.addLocatorHandler()} allows handling an overlay during an
* action that it would block.
*
* <p> This method registers a handler for an overlay that is executed once the locator is visible on the page. The handler
* should get rid of the overlay so that actions blocked by it can proceed. This is useful for nondeterministic
* interstitial pages or dialogs, like a cookie consent dialog.
*
* <p> Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
*
* <p> You can register multiple handlers. However, only a single handler will be running at a time. Any actions inside a
* handler must not require another handler to run.
*
* <p> <strong>NOTE:</strong> Running the interceptor will alter your page state mid-test. For example it will change the currently focused element
* and move the mouse. Make sure that the actions that run after the interceptor are self-contained and do not rely on the
* focus and mouse state. <br /> <br /> For example, consider a test that calls {@link Locator#focus Locator.focus()}
* followed by {@link Keyboard#press Keyboard.press()}. If your handler clicks a button between these two actions, the
* focused element most likely will be wrong, and key press will happen on the unexpected element. Use {@link Locator#press
* Locator.press()} instead to avoid this problem. <br /> <br /> Another example is a series of mouse actions, where {@link
* Mouse#move Mouse.move()} is followed by {@link Mouse#down Mouse.down()}. Again, when the handler runs between these two
* actions, the mouse position will be wrong during the mouse down. Prefer methods like {@link Locator#click
* Locator.click()} that are self-contained.
*
* <p> **Usage**
*
* <p> An example that closes a cookie dialog when it appears:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example that skips the "Confirm your security details" page when it is shown:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example with a custom callback on every actionability check. It uses a {@code <body>} locator that is always visible,
* so the handler is called before every actionability check:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.locator("body")), () => {
* page.evaluate("window.removeObstructionsForTestIfNeeded()");
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* @param locator Locator that triggers the handler.
* @param handler Function that should be run once {@code locator} appears. This function should get rid of the element that blocks
* actions like click.
* @since v1.42
*/
void addLocatorHandler(Locator locator, Runnable handler);
/**
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ ElementHandle addScriptTagImpl(AddScriptTagOptions options) {
throw new PlaywrightException("Failed to read from file", e);
}
String content = new String(encoded, StandardCharsets.UTF_8);
content += "//# sourceURL=" + options.path.toString().replace("\n", "");
content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ void handle(Route route) {

if ("fulfill".equals(action)) {
int status = response.get("status").getAsInt();
// If the response status is -1, the request was canceled or stalled, so we just stall it here.
// See https://github.com/microsoft/playwright/issues/29311.
// TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
// because the request might have been stalled for a long time until the very end of the
// test when HAR was recorded but we'd abort it immediately.
if (status == -1) {
return;
}
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final Map<Integer, Runnable> locatorHandlers = new HashMap<>();

private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
Expand Down Expand Up @@ -170,6 +172,9 @@ protected void handleEvent(String event, JsonObject params) {
frame.parentFrame.childFrames.remove(frame);
}
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("locatorHandlerTriggered".equals(event)) {
int uid = params.get("uid").getAsInt();
onLocatorHandlerTriggered(uid);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
Expand Down Expand Up @@ -523,6 +528,34 @@ public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}

@Override
public void addLocatorHandler(Locator locator, Runnable handler) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
if (locatorImpl.frame != mainFrame) {
throw new PlaywrightException("Locator must belong to the main frame of this page");
}
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, handler);
});
}

private void onLocatorHandlerTriggered(int uid) {
try {
Runnable handler = locatorHandlers.get(uid);
if (handler != null) {
handler.run();
}
} finally {
JsonObject params = new JsonObject();
params.addProperty("uid", uid);
sendMessageAsync("resolveLocatorHandlerNoReply", params);
}
}

@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
Expand All @@ -544,7 +577,8 @@ public void addInitScript(Path path) {
withLogging("Page.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,8 @@ static int fromJsRegexFlags(String regexFlags) {
}
return flags;
}

static String addSourceUrlToScript(String source, Path path) {
return source + "\n//# sourceURL=" + path.toString().replace("\n", "");
}
}
Loading

0 comments on commit 7f2a5fe

Please sign in to comment.