mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: add methods to configure locators (#10273)
This commit is contained in:
parent
b03acac30f
commit
54d6192262
@ -22,18 +22,42 @@ import {isErrorLike} from '../util/ErrorLike.js';
|
|||||||
import {ElementHandle, BoundingBox, ClickOptions} from './ElementHandle.js';
|
import {ElementHandle, BoundingBox, ClickOptions} from './ElementHandle.js';
|
||||||
import type {Page} from './Page.js';
|
import type {Page} from './Page.js';
|
||||||
|
|
||||||
|
type VisibilityOption = 'hidden' | 'visible' | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export interface LocatorOptions {
|
export interface LocatorOptions {
|
||||||
/**
|
/**
|
||||||
* Whether to wait for the element to be `visible` or `hidden`.
|
* Whether to wait for the element to be `visible` or `hidden`. `null` to
|
||||||
|
* disable visibility checks.
|
||||||
*/
|
*/
|
||||||
visibility: 'hidden' | 'visible';
|
visibility: VisibilityOption;
|
||||||
/**
|
/**
|
||||||
* Total timeout for the entire locator operation.
|
* Total timeout for the entire locator operation.
|
||||||
|
*
|
||||||
|
* Pass `0` to disable timeout.
|
||||||
|
*
|
||||||
|
* @defaultValue `Page.getDefaultTimeout()`
|
||||||
*/
|
*/
|
||||||
timeout: number;
|
timeout: number;
|
||||||
|
/**
|
||||||
|
* Whether to scroll the element into viewport if not in the viewprot already.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
ensureElementIsInTheViewport: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to wait for input elements to become enabled before the action.
|
||||||
|
* Applicable to `click` and `fill` actions.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
waitForEnabled: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to wait for the element's bounding box to be same between two
|
||||||
|
* animation frames.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
waitForStableBoundingBox: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,6 +122,9 @@ export class Locator extends EventEmitter {
|
|||||||
options: LocatorOptions = {
|
options: LocatorOptions = {
|
||||||
visibility: 'visible',
|
visibility: 'visible',
|
||||||
timeout: page.getDefaultTimeout(),
|
timeout: page.getDefaultTimeout(),
|
||||||
|
ensureElementIsInTheViewport: true,
|
||||||
|
waitForEnabled: true,
|
||||||
|
waitForStableBoundingBox: true,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -127,6 +154,31 @@ export class Locator extends EventEmitter {
|
|||||||
return super.off(eventName, handler);
|
return super.off(eventName, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVisibility(visibility: VisibilityOption): this {
|
||||||
|
this.#options.visibility = visibility;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(timeout: number): this {
|
||||||
|
this.#options.timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnsureElementIsInTheViewport(value: boolean): this {
|
||||||
|
this.#options.ensureElementIsInTheViewport = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWaitForEnabled(value: boolean): this {
|
||||||
|
this.#options.waitForEnabled = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWaitForStableBoundingBox(value: boolean): this {
|
||||||
|
this.#options.waitForStableBoundingBox = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retries the `fn` until a truthy result is returned.
|
* Retries the `fn` until a truthy result is returned.
|
||||||
*/
|
*/
|
||||||
@ -138,10 +190,12 @@ export class Locator extends EventEmitter {
|
|||||||
let isActive = true;
|
let isActive = true;
|
||||||
let controller: AbortController;
|
let controller: AbortController;
|
||||||
// If the loop times out, we abort only the last iteration's controller.
|
// If the loop times out, we abort only the last iteration's controller.
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = timeout
|
||||||
|
? setTimeout(() => {
|
||||||
isActive = false;
|
isActive = false;
|
||||||
controller?.abort();
|
controller?.abort();
|
||||||
}, timeout);
|
}, timeout)
|
||||||
|
: 0;
|
||||||
// If the user's signal aborts, we abort the last iteration and the loop.
|
// If the user's signal aborts, we abort the last iteration and the loop.
|
||||||
signal?.addEventListener(
|
signal?.addEventListener(
|
||||||
'abort',
|
'abort',
|
||||||
@ -194,6 +248,9 @@ export class Locator extends EventEmitter {
|
|||||||
element: ElementHandle,
|
element: ElementHandle,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (!this.#options.ensureElementIsInTheViewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Side-effect: this also checks if it is connected.
|
// Side-effect: this also checks if it is connected.
|
||||||
const isIntersectingViewport = await element.isIntersectingViewport({
|
const isIntersectingViewport = await element.isIntersectingViewport({
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
@ -221,6 +278,9 @@ export class Locator extends EventEmitter {
|
|||||||
element: ElementHandle,
|
element: ElementHandle,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (this.#options.visibility === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.#options.visibility === 'hidden') {
|
if (this.#options.visibility === 'hidden') {
|
||||||
await this.#waitForFunction(async () => {
|
await this.#waitForFunction(async () => {
|
||||||
return element.isHidden();
|
return element.isHidden();
|
||||||
@ -239,6 +299,9 @@ export class Locator extends EventEmitter {
|
|||||||
element: ElementHandle,
|
element: ElementHandle,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (!this.#options.waitForEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this.#page.waitForFunction(
|
await this.#page.waitForFunction(
|
||||||
el => {
|
el => {
|
||||||
if (['button', 'textarea', 'input', 'select'].includes(el.tagName)) {
|
if (['button', 'textarea', 'input', 'select'].includes(el.tagName)) {
|
||||||
@ -262,6 +325,9 @@ export class Locator extends EventEmitter {
|
|||||||
element: ElementHandle,
|
element: ElementHandle,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (!this.#options.waitForStableBoundingBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
function getClientRect() {
|
function getClientRect() {
|
||||||
return element.evaluate(el => {
|
return element.evaluate(el => {
|
||||||
return new Promise<[BoundingBox, BoundingBox]>(resolve => {
|
return new Promise<[BoundingBox, BoundingBox]>(resolve => {
|
||||||
|
@ -29,6 +29,33 @@ describe('Locator', function () {
|
|||||||
setupTestBrowserHooks();
|
setupTestBrowserHooks();
|
||||||
setupTestPageAndContextHooks();
|
setupTestPageAndContextHooks();
|
||||||
|
|
||||||
|
it('should work without preconditions', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
await page.setViewport({width: 500, height: 500});
|
||||||
|
await page.setContent(`
|
||||||
|
<button onclick="this.innerText = 'clicked';">test</button>
|
||||||
|
`);
|
||||||
|
let willClick = false;
|
||||||
|
await page
|
||||||
|
.locator('button')
|
||||||
|
.setEnsureElementIsInTheViewport(false)
|
||||||
|
.setTimeout(0)
|
||||||
|
.setVisibility(null)
|
||||||
|
.setWaitForEnabled(false)
|
||||||
|
.setWaitForStableBoundingBox(false)
|
||||||
|
.on(LocatorEmittedEvents.Action, () => {
|
||||||
|
willClick = true;
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
const button = await page.$('button');
|
||||||
|
const text = await button?.evaluate(el => {
|
||||||
|
return el.innerText;
|
||||||
|
});
|
||||||
|
expect(text).toBe('clicked');
|
||||||
|
expect(willClick).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Locator.click', function () {
|
describe('Locator.click', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
Loading…
Reference in New Issue
Block a user