chore: rename handleLocator to addLocatorHandler (#29443)

This commit is contained in:
Dmitry Gozman 2024-02-09 15:18:50 -08:00 committed by GitHub
parent 61b88a851d
commit f0244b8a76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 109 additions and 107 deletions

View file

@ -3131,12 +3131,12 @@ return value resolves to `[]`.
* since: v1.9
## async method: Page.handleLocator
## async method: Page.addLocatorHandler
* since: v1.42
Registers a handler for an element that might block certain actions like click. The handler should get rid of the blocking element so that an action may proceed. This is useful for nondeterministic interstitial pages or dialogs, like a cookie consent dialog.
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, [`method: Page.addLocatorHandler`] allows handling an overlay during an action that it would block.
The handler will be executed before the [actionability checks](../actionability.md) for each action, as well as before each probe of the [web assertions](../test-assertions.md). When no actions are executed and no assertions are probed, the handler does not run at all, even if the given locator appears on the page. Actions that pass the `force` option do not trigger the handler.
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.
Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
@ -3158,7 +3158,7 @@ An example that closes a cookie dialog when it appears:
```js
// Setup the handler.
await page.handleLocator(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
await page.getByRole('button', { name: 'Reject all cookies' }).click();
});
@ -3169,7 +3169,7 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java
// Setup the handler.
page.handleLocator(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
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();
});
@ -3182,7 +3182,7 @@ page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
# Setup the handler.
def handler():
page.get_by_role("button", name="Reject all cookies").click()
page.handle_locator(page.get_by_role("button", name="Accept all cookies"), handler)
page.add_locator_handler(page.get_by_role("button", name="Accept all cookies"), handler)
# Write the test as usual.
page.goto("https://example.com")
@ -3193,7 +3193,7 @@ page.get_by_role("button", name="Start here").click()
# Setup the handler.
def handler():
await page.get_by_role("button", name="Reject all cookies").click()
await page.handle_locator(page.get_by_role("button", name="Accept all cookies"), handler)
await page.add_locator_handler(page.get_by_role("button", name="Accept all cookies"), handler)
# Write the test as usual.
await page.goto("https://example.com")
@ -3202,7 +3202,7 @@ await page.get_by_role("button", name="Start here").click()
```csharp
// Setup the handler.
await page.HandleLocatorAsync(page.GetByRole(AriaRole.Button, new() { Name = "Accept all cookies" }), async () => {
await page.AddLocatorHandlerAsync(page.GetByRole(AriaRole.Button, new() { Name = "Accept all cookies" }), async () => {
await page.GetByRole(AriaRole.Button, new() { Name = "Reject all cookies" }).ClickAsync();
});
@ -3215,7 +3215,7 @@ An example that skips the "Confirm your security details" page when it is shown:
```js
// Setup the handler.
await page.handleLocator(page.getByText('Confirm your security details'), async () => {
await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
await page.getByRole('button', 'Remind me later').click();
});
@ -3226,7 +3226,7 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java
// Setup the handler.
page.handleLocator(page.getByText("Confirm your security details")), () => {
page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
});
@ -3239,7 +3239,7 @@ page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
# Setup the handler.
def handler():
page.get_by_role("button", name="Remind me later").click()
page.handle_locator(page.get_by_text("Confirm your security details"), handler)
page.add_locator_handler(page.get_by_text("Confirm your security details"), handler)
# Write the test as usual.
page.goto("https://example.com")
@ -3250,7 +3250,7 @@ page.get_by_role("button", name="Start here").click()
# Setup the handler.
def handler():
await page.get_by_role("button", name="Remind me later").click()
await page.handle_locator(page.get_by_text("Confirm your security details"), handler)
await page.add_locator_handler(page.get_by_text("Confirm your security details"), handler)
# Write the test as usual.
await page.goto("https://example.com")
@ -3259,7 +3259,7 @@ await page.get_by_role("button", name="Start here").click()
```csharp
// Setup the handler.
await page.HandleLocatorAsync(page.GetByText("Confirm your security details"), async () => {
await page.AddLocatorHandlerAsync(page.GetByText("Confirm your security details"), async () => {
await page.GetByRole(AriaRole.Button, new() { Name = "Remind me later" }).ClickAsync();
});
@ -3272,7 +3272,7 @@ An example with a custom callback on every actionability check. It uses a `<body
```js
// Setup the handler.
await page.handleLocator(page.locator('body'), async () => {
await page.addLocatorHandler(page.locator('body'), async () => {
await page.evaluate(() => window.removeObstructionsForTestIfNeeded());
});
@ -3283,7 +3283,7 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java
// Setup the handler.
page.handleLocator(page.locator("body")), () => {
page.addLocatorHandler(page.locator("body")), () => {
page.evaluate("window.removeObstructionsForTestIfNeeded()");
});
@ -3296,7 +3296,7 @@ page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
# Setup the handler.
def handler():
page.evaluate("window.removeObstructionsForTestIfNeeded()")
page.handle_locator(page.locator("body"), handler)
page.add_locator_handler(page.locator("body"), handler)
# Write the test as usual.
page.goto("https://example.com")
@ -3307,7 +3307,7 @@ page.get_by_role("button", name="Start here").click()
# Setup the handler.
def handler():
await page.evaluate("window.removeObstructionsForTestIfNeeded()")
await page.handle_locator(page.locator("body"), handler)
await page.add_locator_handler(page.locator("body"), handler)
# Write the test as usual.
await page.goto("https://example.com")
@ -3316,7 +3316,7 @@ await page.get_by_role("button", name="Start here").click()
```csharp
// Setup the handler.
await page.HandleLocatorAsync(page.Locator("body"), async () => {
await page.AddLocatorHandlerAsync(page.Locator("body"), async () => {
await page.EvaluateAsync("window.removeObstructionsForTestIfNeeded()");
});
@ -3325,13 +3325,13 @@ await page.GotoAsync("https://example.com");
await page.GetByRole("button", new() { Name = "Start here" }).ClickAsync();
```
### param: Page.handleLocator.locator
### param: Page.addLocatorHandler.locator
* since: v1.42
- `locator` <[Locator]>
Locator that triggers the handler.
### param: Page.handleLocator.handler
### param: Page.addLocatorHandler.handler
* since: v1.42
- `handler` <[function]>

View file

@ -362,7 +362,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
return Response.fromNullable((await this._channel.reload({ ...options, waitUntil })).response);
}
async handleLocator(locator: Locator, handler: Function): Promise<void> {
async addLocatorHandler(locator: Locator, handler: Function): Promise<void> {
if (locator._frame !== this._mainFrame)
throw new Error(`Locator must belong to the main frame of this page`);
const { uid } = await this._channel.registerLocatorHandler({ selector: locator._selector });

View file

@ -1780,6 +1780,86 @@ export interface Page {
*/
prependListener(event: 'worker', listener: (worker: Worker) => void): this;
/**
* 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,
* [page.addLocatorHandler(locator, handler)](https://playwright.dev/docs/api/class-page#page-add-locator-handler)
* allows handling an overlay during an action that it would block.
*
* 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.
*
* Note that execution time of the handler counts towards the timeout of the action/assertion that executed the
* handler.
*
* 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.
*
* **NOTE** 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
* [locator.focus([options])](https://playwright.dev/docs/api/class-locator#locator-focus) followed by
* [keyboard.press(key[, options])](https://playwright.dev/docs/api/class-keyboard#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
* [locator.press(key[, options])](https://playwright.dev/docs/api/class-locator#locator-press) instead to avoid this
* problem. <br /> <br /> Another example is a series of mouse actions, where
* [mouse.move(x, y[, options])](https://playwright.dev/docs/api/class-mouse#mouse-move) is followed by
* [mouse.down([options])](https://playwright.dev/docs/api/class-mouse#mouse-down). Again, when the handler runs
* between these two actions, the mouse position will be wrong during the mouse down. Prefer methods like
* [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that are self-contained.
*
* **Usage**
*
* An example that closes a cookie dialog when it appears:
*
* ```js
* // Setup the handler.
* await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
* await page.getByRole('button', { name: 'Reject all cookies' }).click();
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* An example that skips the "Confirm your security details" page when it is shown:
*
* ```js
* // Setup the handler.
* await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
* await page.getByRole('button', 'Remind me later').click();
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* An example with a custom callback on every actionability check. It uses a `<body>` locator that is always visible,
* so the handler is called before every actionability check:
*
* ```js
* // Setup the handler.
* await page.addLocatorHandler(page.locator('body'), async () => {
* await page.evaluate(() => window.removeObstructionsForTestIfNeeded());
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* @param locator Locator that triggers the handler.
* @param handler Function that should be run once `locator` appears. This function should get rid of the element that blocks actions
* like click.
*/
addLocatorHandler(locator: Locator, handler: Function): Promise<void>;
/**
* Adds a `<script>` tag into the page with the desired url or content. Returns the added tag when the script's onload
* fires or when the script content was injected into frame.
@ -2923,84 +3003,6 @@ export interface Page {
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
* Registers a handler for an element that might block certain actions like click. The handler should get rid of the
* blocking element so that an action may proceed. This is useful for nondeterministic interstitial pages or dialogs,
* like a cookie consent dialog.
*
* The handler will be executed before the [actionability checks](https://playwright.dev/docs/actionability) for each action, as well as
* before each probe of the [web assertions](https://playwright.dev/docs/test-assertions). When no actions are executed and no assertions
* are probed, the handler does not run at all, even if the given locator appears on the page. Actions that pass the
* `force` option do not trigger the handler.
*
* Note that execution time of the handler counts towards the timeout of the action/assertion that executed the
* handler.
*
* 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.
*
* **NOTE** 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
* [locator.focus([options])](https://playwright.dev/docs/api/class-locator#locator-focus) followed by
* [keyboard.press(key[, options])](https://playwright.dev/docs/api/class-keyboard#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
* [locator.press(key[, options])](https://playwright.dev/docs/api/class-locator#locator-press) instead to avoid this
* problem. <br /> <br /> Another example is a series of mouse actions, where
* [mouse.move(x, y[, options])](https://playwright.dev/docs/api/class-mouse#mouse-move) is followed by
* [mouse.down([options])](https://playwright.dev/docs/api/class-mouse#mouse-down). Again, when the handler runs
* between these two actions, the mouse position will be wrong during the mouse down. Prefer methods like
* [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that are self-contained.
*
* **Usage**
*
* An example that closes a cookie dialog when it appears:
*
* ```js
* // Setup the handler.
* await page.handleLocator(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
* await page.getByRole('button', { name: 'Reject all cookies' }).click();
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* An example that skips the "Confirm your security details" page when it is shown:
*
* ```js
* // Setup the handler.
* await page.handleLocator(page.getByText('Confirm your security details'), async () => {
* await page.getByRole('button', 'Remind me later').click();
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* An example with a custom callback on every actionability check. It uses a `<body>` locator that is always visible,
* so the handler is called before every actionability check:
*
* ```js
* // Setup the handler.
* await page.handleLocator(page.locator('body'), async () => {
* await page.evaluate(() => window.removeObstructionsForTestIfNeeded());
* });
*
* // Write the test as usual.
* await page.goto('https://example.com');
* await page.getByRole('button', { name: 'Start here' }).click();
* ```
*
* @param locator Locator that triggers the handler.
* @param handler Function that should be run once `locator` appears. This function should get rid of the element that blocks actions
* like click.
*/
handleLocator(locator: Locator, handler: Function): Promise<void>;
/**
* **NOTE** Use locator-based [locator.hover([options])](https://playwright.dev/docs/api/class-locator#locator-hover) instead.
* Read more about [locators](https://playwright.dev/docs/locators).

View file

@ -22,7 +22,7 @@ test('should work', async ({ page, server }) => {
let beforeCount = 0;
let afterCount = 0;
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
++beforeCount;
await page.locator('#close').click();
++afterCount;
@ -61,7 +61,7 @@ test('should work', async ({ page, server }) => {
test('should work with a custom check', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
await page.handleLocator(page.locator('body'), async () => {
await page.addLocatorHandler(page.locator('body'), async () => {
if (await page.getByText('This interstitial covers the button').isVisible())
await page.locator('#close').click();
});
@ -88,7 +88,7 @@ test('should work with a custom check', async ({ page, server }) => {
test('should work with locator.hover()', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
await page.locator('#close').click();
});
@ -104,7 +104,7 @@ test('should work with locator.hover()', async ({ page, server }) => {
test('should not work with force:true', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
await page.locator('#close').click();
});
@ -120,7 +120,7 @@ test('should not work with force:true', async ({ page, server }) => {
test('should throw when page closes', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
await page.close();
});
@ -137,7 +137,7 @@ test('should throw when handler times out', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
let called = 0;
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
++called;
// Deliberately timeout.
await new Promise(() => {});
@ -162,7 +162,7 @@ test('should work with toBeVisible', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/handle-locator.html');
let called = 0;
await page.handleLocator(page.getByText('This interstitial covers the button'), async () => {
await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => {
++called;
await page.locator('#close').click();
});
@ -196,7 +196,7 @@ test('should work with toHaveScreenshot', async ({ page, server }) => {
closeButton.addEventListener('click', () => overlay.remove());
});
await page.handleLocator(page.getByRole('button', { name: 'close' }), async () => {
await page.addLocatorHandler(page.getByRole('button', { name: 'close' }), async () => {
await page.getByRole('button', { name: 'close' }).click();
});