mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: implement waitForFrame
and use clickablePoint
for ElementHandle operations (#10778)
This commit is contained in:
parent
a4a2cf1d39
commit
c4a4412920
@ -10,9 +10,9 @@ This method scrolls element into view if needed, and then uses [Page.mouse](./pu
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract click(
|
||||
click(
|
||||
this: ElementHandle<Element>,
|
||||
options?: ClickOptions
|
||||
options?: Readonly<ClickOptions>
|
||||
): Promise<void>;
|
||||
}
|
||||
```
|
||||
@ -22,7 +22,7 @@ class ElementHandle {
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------------------------------------------------------------ | ------------ |
|
||||
| this | [ElementHandle](./puppeteer.elementhandle.md)<Element> | |
|
||||
| options | [ClickOptions](./puppeteer.clickoptions.md) | _(Optional)_ |
|
||||
| options | Readonly<[ClickOptions](./puppeteer.clickoptions.md)> | _(Optional)_ |
|
||||
|
||||
**Returns:**
|
||||
|
||||
|
@ -10,7 +10,7 @@ This method scrolls element into view if needed, and then uses [Page](./puppetee
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract hover(this: ElementHandle<Element>): Promise<void>;
|
||||
hover(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -10,10 +10,7 @@ Focuses the element, and then uses [Keyboard.down()](./puppeteer.keyboard.down.m
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract press(
|
||||
key: KeyInput,
|
||||
options?: Readonly<KeyPressOptions>
|
||||
): Promise<void>;
|
||||
press(key: KeyInput, options?: Readonly<KeyPressOptions>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -10,7 +10,7 @@ This method scrolls element into view if needed, and then uses [Touchscreen.tap(
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract tap(this: ElementHandle<Element>): Promise<void>;
|
||||
tap(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchEnd
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract touchEnd(this: ElementHandle<Element>): Promise<void>;
|
||||
touchEnd(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchMove
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract touchMove(this: ElementHandle<Element>): Promise<void>;
|
||||
touchMove(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchStart
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract touchStart(this: ElementHandle<Element>): Promise<void>;
|
||||
touchStart(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -12,10 +12,7 @@ To press a special key, like `Control` or `ArrowDown`, use [ElementHandle.press(
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
abstract type(
|
||||
text: string,
|
||||
options?: Readonly<KeyboardTypeOptions>
|
||||
): Promise<void>;
|
||||
type(text: string, options?: Readonly<KeyboardTypeOptions>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -157,7 +157,7 @@ page.off('request', logRequest);
|
||||
| [viewport()](./puppeteer.page.viewport.md) | | Current page viewport settings. |
|
||||
| [waitForDevicePrompt(options)](./puppeteer.page.waitfordeviceprompt.md) | | <p>This method is typically coupled with an action that triggers a device request from an api such as WebBluetooth.</p><p>:::caution</p><p>This must be called before the device request is made. It will not return a currently active device prompt.</p><p>:::</p> |
|
||||
| [waitForFileChooser(options)](./puppeteer.page.waitforfilechooser.md) | | <p>This method is typically coupled with an action that triggers file choosing.</p><p>:::caution</p><p>This must be called before the file chooser is launched. It will not return a currently active file chooser.</p><p>:::</p> |
|
||||
| [waitForFrame(urlOrPredicate, options)](./puppeteer.page.waitforframe.md) | | |
|
||||
| [waitForFrame(urlOrPredicate, options)](./puppeteer.page.waitforframe.md) | | Waits for a frame matching the given conditions to appear. |
|
||||
| [waitForFunction(pageFunction, options, args)](./puppeteer.page.waitforfunction.md) | | Waits for a function to finish evaluating in the page's context. |
|
||||
| [waitForNavigation(options)](./puppeteer.page.waitfornavigation.md) | | Waits for the page to navigate to a new URL or to reload. It is useful when you run code that will indirectly cause the page to navigate. |
|
||||
| [waitForNetworkIdle(options)](./puppeteer.page.waitfornetworkidle.md) | | |
|
||||
|
@ -4,15 +4,15 @@ sidebar_label: Page.waitForFrame
|
||||
|
||||
# Page.waitForFrame() method
|
||||
|
||||
Waits for a frame matching the given conditions to appear.
|
||||
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
class Page {
|
||||
waitForFrame(
|
||||
urlOrPredicate: string | ((frame: Frame) => boolean | Promise<boolean>),
|
||||
options?: {
|
||||
timeout?: number;
|
||||
}
|
||||
urlOrPredicate: string | ((frame: Frame) => Awaitable<boolean>),
|
||||
options?: WaitTimeoutOptions
|
||||
): Promise<Frame>;
|
||||
}
|
||||
```
|
||||
@ -20,22 +20,14 @@ class Page {
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| -------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------- |
|
||||
| urlOrPredicate | string \| ((frame: [Frame](./puppeteer.frame.md)) => boolean \| Promise<boolean>) | A URL or predicate to wait for. |
|
||||
| options | { timeout?: number; } | _(Optional)_ Optional waiting parameters |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------- | ------------ |
|
||||
| urlOrPredicate | string \| ((frame: [Frame](./puppeteer.frame.md)) => [Awaitable](./puppeteer.awaitable.md)<boolean>) | |
|
||||
| options | [WaitTimeoutOptions](./puppeteer.waittimeoutoptions.md) | _(Optional)_ |
|
||||
|
||||
**Returns:**
|
||||
|
||||
Promise<[Frame](./puppeteer.frame.md)>
|
||||
|
||||
Promise which resolves to the matched frame.
|
||||
|
||||
## Remarks
|
||||
|
||||
Optional Parameter have:
|
||||
|
||||
- `timeout`: Maximum wait time in milliseconds, defaults to `30` seconds, pass `0` to disable the timeout. The default value can be changed by using the [Page.setDefaultTimeout()](./puppeteer.page.setdefaulttimeout.md) method.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
|
@ -652,17 +652,25 @@ export abstract class ElementHandle<
|
||||
* uses {@link Page} to hover over the center of the element.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
abstract hover(this: ElementHandle<Element>): Promise<void>;
|
||||
async hover(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.frame.page().mouse.move(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method scrolls element into view if needed, and then
|
||||
* uses {@link Page | Page.mouse} to click in the center of the element.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
abstract click(
|
||||
async click(
|
||||
this: ElementHandle<Element>,
|
||||
options?: ClickOptions
|
||||
): Promise<void>;
|
||||
options: Readonly<ClickOptions> = {}
|
||||
): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint(options.offset);
|
||||
await this.frame.page().mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates and captures a dragevent from the element.
|
||||
@ -804,13 +812,29 @@ export abstract class ElementHandle<
|
||||
* {@link Touchscreen.tap} to tap in the center of the element.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
abstract tap(this: ElementHandle<Element>): Promise<void>;
|
||||
async tap(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.frame.page().touchscreen.touchStart(x, y);
|
||||
await this.frame.page().touchscreen.touchEnd();
|
||||
}
|
||||
|
||||
abstract touchStart(this: ElementHandle<Element>): Promise<void>;
|
||||
async touchStart(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.frame.page().touchscreen.touchStart(x, y);
|
||||
}
|
||||
|
||||
abstract touchMove(this: ElementHandle<Element>): Promise<void>;
|
||||
async touchMove(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.frame.page().touchscreen.touchMove(x, y);
|
||||
}
|
||||
|
||||
abstract touchEnd(this: ElementHandle<Element>): Promise<void>;
|
||||
async touchEnd(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
await this.frame.page().touchscreen.touchEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
|
||||
@ -849,10 +873,13 @@ export abstract class ElementHandle<
|
||||
*
|
||||
* @param options - Delay in milliseconds. Defaults to 0.
|
||||
*/
|
||||
abstract type(
|
||||
async type(
|
||||
text: string,
|
||||
options?: Readonly<KeyboardTypeOptions>
|
||||
): Promise<void>;
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.frame.page().keyboard.type(text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the element, and then uses {@link Keyboard.down} and {@link Keyboard.up}.
|
||||
@ -868,26 +895,29 @@ export abstract class ElementHandle<
|
||||
* @param key - Name of key to press, such as `ArrowLeft`.
|
||||
* See {@link KeyInput} for a list of all key names.
|
||||
*/
|
||||
abstract press(
|
||||
async press(
|
||||
key: KeyInput,
|
||||
options?: Readonly<KeyPressOptions>
|
||||
): Promise<void>;
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.frame.page().keyboard.press(key, options);
|
||||
}
|
||||
|
||||
async #clickableBox(): Promise<BoundingBox | null> {
|
||||
const adoptedThis = await this.frame.isolatedRealm().adoptHandle(this);
|
||||
const rects = await adoptedThis.evaluate(element => {
|
||||
const boxes = await adoptedThis.evaluate(element => {
|
||||
if (!(element instanceof Element)) {
|
||||
return null;
|
||||
}
|
||||
return [...element.getClientRects()].map(rect => {
|
||||
return rect.toJSON();
|
||||
}) as DOMRect[];
|
||||
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
|
||||
});
|
||||
});
|
||||
void adoptedThis.dispose().catch(debugError);
|
||||
if (!rects?.length) {
|
||||
if (!boxes?.length) {
|
||||
return null;
|
||||
}
|
||||
await this.#intersectBoundingBoxesWithFrame(rects);
|
||||
await this.#intersectBoundingBoxesWithFrame(boxes);
|
||||
let frame: Frame | null | undefined = this.frame;
|
||||
let element: HandleFor<HTMLIFrameElement> | null | undefined;
|
||||
while ((element = await frame?.frameElement())) {
|
||||
@ -914,27 +944,27 @@ export abstract class ElementHandle<
|
||||
if (!parentBox) {
|
||||
return null;
|
||||
}
|
||||
for (const box of rects) {
|
||||
for (const box of boxes) {
|
||||
box.x += parentBox.left;
|
||||
box.y += parentBox.top;
|
||||
}
|
||||
await element.#intersectBoundingBoxesWithFrame(rects);
|
||||
await element.#intersectBoundingBoxesWithFrame(boxes);
|
||||
frame = frame?.parentFrame();
|
||||
} finally {
|
||||
void element.dispose().catch(debugError);
|
||||
}
|
||||
}
|
||||
const rect = rects.find(box => {
|
||||
const box = boxes.find(box => {
|
||||
return box.width >= 1 && box.height >= 1;
|
||||
});
|
||||
if (!rect) {
|
||||
if (!box) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
height: rect.height,
|
||||
width: rect.width,
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
height: box.height,
|
||||
width: box.width,
|
||||
};
|
||||
}
|
||||
|
||||
@ -967,7 +997,7 @@ export abstract class ElementHandle<
|
||||
return null;
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
return rect.toJSON() as DOMRect;
|
||||
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
|
||||
});
|
||||
void adoptedThis.dispose().catch(debugError);
|
||||
if (!box) {
|
||||
@ -977,11 +1007,9 @@ export abstract class ElementHandle<
|
||||
if (!offset) {
|
||||
return null;
|
||||
}
|
||||
box.x += offset.x;
|
||||
box.y += offset.y;
|
||||
return {
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
x: box.x + offset.x,
|
||||
y: box.y + offset.y,
|
||||
height: box.height,
|
||||
width: box.width,
|
||||
};
|
||||
|
@ -18,6 +18,18 @@ import type {Readable} from 'stream';
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {
|
||||
filterAsync,
|
||||
first,
|
||||
firstValueFrom,
|
||||
from,
|
||||
fromEvent,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
raceWith,
|
||||
timer,
|
||||
} from '../../third_party/rxjs/rxjs.js';
|
||||
import type {HTTPRequest} from '../api/HTTPRequest.js';
|
||||
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import type {Accessibility} from '../common/Accessibility.js';
|
||||
@ -26,7 +38,7 @@ import type {ConsoleMessage} from '../common/ConsoleMessage.js';
|
||||
import type {Coverage} from '../common/Coverage.js';
|
||||
import {Device} from '../common/Device.js';
|
||||
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||
import {TargetCloseError} from '../common/Errors.js';
|
||||
import {TargetCloseError, TimeoutError} from '../common/Errors.js';
|
||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||
import type {FileChooser} from '../common/FileChooser.js';
|
||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||
@ -1745,9 +1757,8 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param urlOrPredicate - A URL or predicate to wait for.
|
||||
* @param options - Optional waiting parameters
|
||||
* @returns Promise which resolves to the matched frame.
|
||||
* Waits for a frame matching the given conditions to appear.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
@ -1755,20 +1766,41 @@ export class Page extends EventEmitter {
|
||||
* return frame.name() === 'Test';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* Optional Parameter have:
|
||||
*
|
||||
* - `timeout`: Maximum wait time in milliseconds, defaults to `30` seconds,
|
||||
* pass `0` to disable the timeout. The default value can be changed by using
|
||||
* the {@link Page.setDefaultTimeout} method.
|
||||
*/
|
||||
async waitForFrame(
|
||||
urlOrPredicate: string | ((frame: Frame) => boolean | Promise<boolean>),
|
||||
options?: {timeout?: number}
|
||||
): Promise<Frame>;
|
||||
async waitForFrame(): Promise<Frame> {
|
||||
throw new Error('Not implemented');
|
||||
urlOrPredicate: string | ((frame: Frame) => Awaitable<boolean>),
|
||||
options: WaitTimeoutOptions = {}
|
||||
): Promise<Frame> {
|
||||
const {timeout: ms = this.getDefaultTimeout()} = options;
|
||||
|
||||
if (isString(urlOrPredicate)) {
|
||||
urlOrPredicate = (frame: Frame) => {
|
||||
return urlOrPredicate === frame.url();
|
||||
};
|
||||
}
|
||||
|
||||
return firstValueFrom(
|
||||
merge(
|
||||
fromEvent(this, PageEmittedEvents.FrameAttached) as Observable<Frame>,
|
||||
fromEvent(this, PageEmittedEvents.FrameNavigated) as Observable<Frame>,
|
||||
from(this.frames())
|
||||
).pipe(
|
||||
filterAsync(urlOrPredicate),
|
||||
first(),
|
||||
raceWith(
|
||||
timer(ms === 0 ? Infinity : ms).pipe(
|
||||
map(() => {
|
||||
throw new TimeoutError(`Timed out after waiting ${ms}ms`);
|
||||
})
|
||||
),
|
||||
fromEvent(this, PageEmittedEvents.Close).pipe(
|
||||
map(() => {
|
||||
throw new TargetCloseError('Page closed.');
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,13 +16,7 @@
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {
|
||||
AutofillData,
|
||||
ClickOptions,
|
||||
ElementHandle,
|
||||
Point,
|
||||
} from '../api/ElementHandle.js';
|
||||
import {KeyboardTypeOptions, KeyPressOptions} from '../api/Input.js';
|
||||
import {AutofillData, ElementHandle, Point} from '../api/ElementHandle.js';
|
||||
import {Page, ScreenshotOptions} from '../api/Page.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
@ -33,7 +27,6 @@ import {FrameManager} from './FrameManager.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {NodeFor} from './types.js';
|
||||
import {KeyInput} from './USKeyboardLayout.js';
|
||||
import {debugError} from './util.js';
|
||||
|
||||
/**
|
||||
@ -141,31 +134,6 @@ export class CDPElementHandle<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method scrolls element into view if needed, and then
|
||||
* uses {@link Page.mouse} to hover over the center of the element.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
override async hover(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.#page.mouse.move(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method scrolls element into view if needed, and then
|
||||
* uses {@link Page.mouse} to click in the center of the element.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
override async click(
|
||||
this: CDPElementHandle<Element>,
|
||||
options: Readonly<ClickOptions> = {}
|
||||
): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint(options.offset);
|
||||
await this.#page.mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates and captures a dragevent from the element.
|
||||
*/
|
||||
@ -281,46 +249,6 @@ export class CDPElementHandle<
|
||||
}
|
||||
}
|
||||
|
||||
override async tap(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.#page.touchscreen.touchStart(x, y);
|
||||
await this.#page.touchscreen.touchEnd();
|
||||
}
|
||||
|
||||
override async touchStart(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.#page.touchscreen.touchStart(x, y);
|
||||
}
|
||||
|
||||
override async touchMove(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
await this.#page.touchscreen.touchMove(x, y);
|
||||
}
|
||||
|
||||
override async touchEnd(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
await this.#page.touchscreen.touchEnd();
|
||||
}
|
||||
|
||||
override async type(
|
||||
text: string,
|
||||
options?: Readonly<KeyboardTypeOptions>
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#page.keyboard.type(text, options);
|
||||
}
|
||||
|
||||
override async press(
|
||||
key: KeyInput,
|
||||
options?: Readonly<KeyPressOptions>
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#page.keyboard.press(key, options);
|
||||
}
|
||||
|
||||
override async screenshot(
|
||||
this: CDPElementHandle<Element>,
|
||||
options: ScreenshotOptions = {}
|
||||
|
@ -997,53 +997,6 @@ export class CDPPage extends Page {
|
||||
);
|
||||
}
|
||||
|
||||
override async waitForFrame(
|
||||
urlOrPredicate: string | ((frame: Frame) => boolean | Promise<boolean>),
|
||||
options: {timeout?: number} = {}
|
||||
): Promise<Frame> {
|
||||
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||
|
||||
let predicate: (frame: Frame) => Promise<boolean>;
|
||||
if (isString(urlOrPredicate)) {
|
||||
predicate = (frame: Frame) => {
|
||||
return Promise.resolve(urlOrPredicate === frame.url());
|
||||
};
|
||||
} else {
|
||||
predicate = (frame: Frame) => {
|
||||
const value = urlOrPredicate(frame);
|
||||
if (typeof value === 'boolean') {
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
const eventRace: Promise<Frame> = Deferred.race([
|
||||
waitForEvent(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameAttached,
|
||||
predicate,
|
||||
timeout,
|
||||
this.#sessionCloseDeferred.valueOrThrow()
|
||||
),
|
||||
waitForEvent(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameNavigated,
|
||||
predicate,
|
||||
timeout,
|
||||
this.#sessionCloseDeferred.valueOrThrow()
|
||||
),
|
||||
...this.frames().map(async frame => {
|
||||
if (await predicate(frame)) {
|
||||
return frame;
|
||||
}
|
||||
return await eventRace;
|
||||
}),
|
||||
]);
|
||||
|
||||
return eventRace;
|
||||
}
|
||||
|
||||
override async goBack(
|
||||
options: WaitForOptions = {}
|
||||
): Promise<HTTPResponse | null> {
|
||||
|
@ -19,11 +19,7 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
import {
|
||||
AutofillData,
|
||||
ElementHandle as BaseElementHandle,
|
||||
ClickOptions,
|
||||
} from '../../api/ElementHandle.js';
|
||||
import {KeyboardTypeOptions, KeyPressOptions} from '../../api/Input.js';
|
||||
import {assert} from '../../util/assert.js';
|
||||
import {KeyInput} from '../USKeyboardLayout.js';
|
||||
import {debugError} from '../util.js';
|
||||
|
||||
import {Frame} from './Frame.js';
|
||||
@ -105,96 +101,4 @@ export class ElementHandle<
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ///////////////////
|
||||
// // Input methods //
|
||||
// ///////////////////
|
||||
override async click(
|
||||
this: ElementHandle<Element>,
|
||||
options?: Readonly<ClickOptions>
|
||||
): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const {x = 0, y = 0} = options?.offset ?? {};
|
||||
const remoteValue = this.remoteValue();
|
||||
assert('sharedId' in remoteValue);
|
||||
return this.#frame.page().mouse.click(
|
||||
x,
|
||||
y,
|
||||
Object.assign({}, options, {
|
||||
origin: {
|
||||
type: 'element' as const,
|
||||
element: remoteValue as Bidi.Script.SharedReference,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override async hover(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const remoteValue = this.remoteValue();
|
||||
assert('sharedId' in remoteValue);
|
||||
return this.#frame.page().mouse.move(0, 0, {
|
||||
origin: {
|
||||
type: 'element' as const,
|
||||
element: remoteValue as Bidi.Script.SharedReference,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override async tap(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const remoteValue = this.remoteValue();
|
||||
assert('sharedId' in remoteValue);
|
||||
return this.#frame.page().touchscreen.tap(0, 0, {
|
||||
origin: {
|
||||
type: 'element' as const,
|
||||
element: remoteValue as Bidi.Script.SharedReference,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override async touchStart(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const remoteValue = this.remoteValue();
|
||||
assert('sharedId' in remoteValue);
|
||||
return this.#frame.page().touchscreen.touchStart(0, 0, {
|
||||
origin: {
|
||||
type: 'element' as const,
|
||||
element: remoteValue as Bidi.Script.SharedReference,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override async touchMove(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
const remoteValue = this.remoteValue();
|
||||
assert('sharedId' in remoteValue);
|
||||
return this.#frame.page().touchscreen.touchMove(0, 0, {
|
||||
origin: {
|
||||
type: 'element' as const,
|
||||
element: remoteValue as Bidi.Script.SharedReference,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override async touchEnd(this: ElementHandle<Element>): Promise<void> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
await this.#frame.page().touchscreen.touchEnd();
|
||||
}
|
||||
|
||||
override async type(
|
||||
text: string,
|
||||
options?: Readonly<KeyboardTypeOptions>
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#frame.page().keyboard.type(text, options);
|
||||
}
|
||||
|
||||
override async press(
|
||||
key: KeyInput,
|
||||
options?: Readonly<KeyPressOptions>
|
||||
): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#frame.page().keyboard.press(key, options);
|
||||
}
|
||||
}
|
||||
|
@ -483,9 +483,10 @@ export class Mouse extends BaseMouse {
|
||||
y: number,
|
||||
options: Readonly<BidiMouseMoveOptions> = {}
|
||||
): Promise<void> {
|
||||
// https://w3c.github.io/webdriver-bidi/#command-input-performActions:~:text=input.PointerMoveAction%20%3D%20%7B%0A%20%20type%3A%20%22pointerMove%22%2C%0A%20%20x%3A%20js%2Dint%2C
|
||||
this.#lastMovePoint = {
|
||||
x,
|
||||
y,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
};
|
||||
await this.#context.connection.send('input.performActions', {
|
||||
context: this.#context.id,
|
||||
@ -496,8 +497,7 @@ export class Mouse extends BaseMouse {
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x,
|
||||
y,
|
||||
...this.#lastMovePoint,
|
||||
duration: (options.steps ?? 0) * 50,
|
||||
origin: options.origin,
|
||||
},
|
||||
@ -551,8 +551,8 @@ export class Mouse extends BaseMouse {
|
||||
const actions: Bidi.Input.PointerSourceAction[] = [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x,
|
||||
y,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
origin: options.origin,
|
||||
},
|
||||
];
|
||||
@ -653,8 +653,8 @@ export class Touchscreen extends BaseTouchscreen {
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x,
|
||||
y,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
origin: options.origin,
|
||||
},
|
||||
{
|
||||
@ -684,8 +684,8 @@ export class Touchscreen extends BaseTouchscreen {
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x,
|
||||
y,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
origin: options.origin,
|
||||
},
|
||||
],
|
||||
|
@ -32,6 +32,7 @@ import {CDPElementHandle} from './ElementHandle.js';
|
||||
import type {CommonEventEmitter} from './EventEmitter.js';
|
||||
import type {ExecutionContext} from './ExecutionContext.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {Awaitable} from './types.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -381,7 +382,7 @@ export const isDate = (obj: unknown): obj is Date => {
|
||||
export async function waitForEvent<T>(
|
||||
emitter: CommonEventEmitter,
|
||||
eventName: string | symbol,
|
||||
predicate: (event: T) => Promise<boolean> | boolean,
|
||||
predicate: (event: T) => Awaitable<boolean>,
|
||||
timeout: number,
|
||||
abortPromise: Promise<Error> | Deferred<Error>
|
||||
): Promise<T> {
|
||||
|
17
packages/puppeteer-core/third_party/rxjs/rxjs.ts
vendored
17
packages/puppeteer-core/third_party/rxjs/rxjs.ts
vendored
@ -39,3 +39,20 @@ export {
|
||||
pipe,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
|
||||
import {mergeMap, from, filter, map, type Observable} from 'rxjs';
|
||||
|
||||
export function filterAsync<T>(
|
||||
predicate: (value: T) => boolean | PromiseLike<boolean>
|
||||
) {
|
||||
return mergeMap<T, Observable<T>>(value => {
|
||||
return from(Promise.resolve(predicate(value))).pipe(
|
||||
filter(isMatch => {
|
||||
return isMatch;
|
||||
}),
|
||||
map(() => {
|
||||
return value;
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -653,12 +653,30 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for <br> elements",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for detached nodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for hidden nodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for recursively hidden nodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -671,6 +689,12 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -1049,12 +1073,6 @@
|
||||
"parameters": ["firefox"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[locator.spec] Locator Locator.click should work with a OOPIF",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[mouse.spec] Mouse should click the document",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -1919,6 +1937,18 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[click.spec] Page.click should click the button inside an iframe",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[click.spec] Page.click should click the button with deviceScaleFactor set",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[click.spec] Page.click should click the button with fixed position inside an iframe",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -2099,18 +2129,6 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for hidden nodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should throw for recursively hidden nodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should work for iframes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -2147,12 +2165,6 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[emulation.spec] Emulation Page.emulateMediaFeatures should throw in case of bad argument",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -2747,6 +2759,18 @@
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[locator.spec] Locator Locator.click should work with a OOPIF",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[locator.spec] Locator Locator.click should work with a OOPIF",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL", "TIMEOUT"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[locator.spec] Locator Locator.race races multiple locators",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -3377,6 +3401,48 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF clickablePoint, boundingBox, boxModel should work for elements inside OOPIFs",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should provide access to elements",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should support evaluating in oop iframes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should support frames within OOP frames",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should support frames within OOP iframes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should track navigations within OOP iframes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF should treat OOP iframes and normal iframes the same",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[oopif.spec] OOPIF-debug OOPIF should support wait for navigation for transitions from local to OOPIF",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
Loading…
Reference in New Issue
Block a user