chore: implement waitForFrame and use clickablePoint for ElementHandle operations (#10778)

This commit is contained in:
jrandolf 2023-08-24 20:32:29 +02:00 committed by GitHub
parent a4a2cf1d39
commit c4a4412920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 245 additions and 330 deletions

View File

@ -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)&lt;Element&gt; | |
| options | [ClickOptions](./puppeteer.clickoptions.md) | _(Optional)_ |
| options | Readonly&lt;[ClickOptions](./puppeteer.clickoptions.md)&gt; | _(Optional)_ |
**Returns:**

View File

@ -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>;
}
```

View File

@ -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>;
}
```

View File

@ -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>;
}
```

View File

@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchEnd
```typescript
class ElementHandle {
abstract touchEnd(this: ElementHandle<Element>): Promise<void>;
touchEnd(this: ElementHandle<Element>): Promise<void>;
}
```

View File

@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchMove
```typescript
class ElementHandle {
abstract touchMove(this: ElementHandle<Element>): Promise<void>;
touchMove(this: ElementHandle<Element>): Promise<void>;
}
```

View File

@ -8,7 +8,7 @@ sidebar_label: ElementHandle.touchStart
```typescript
class ElementHandle {
abstract touchStart(this: ElementHandle<Element>): Promise<void>;
touchStart(this: ElementHandle<Element>): Promise<void>;
}
```

View File

@ -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>;
}
```

View File

@ -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) | | |

View File

@ -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)) =&gt; boolean \| Promise&lt;boolean&gt;) | A URL or predicate to wait for. |
| options | { timeout?: number; } | _(Optional)_ Optional waiting parameters |
| -------------- | ------------------------------------------------------------------------------------------------------------- | ------------ |
| urlOrPredicate | string \| ((frame: [Frame](./puppeteer.frame.md)) =&gt; [Awaitable](./puppeteer.awaitable.md)&lt;boolean&gt;) | |
| options | [WaitTimeoutOptions](./puppeteer.waittimeoutoptions.md) | _(Optional)_ |
**Returns:**
Promise&lt;[Frame](./puppeteer.frame.md)&gt;
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

View File

@ -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,
};

View File

@ -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.');
})
)
)
)
);
}
/**

View File

@ -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 = {}

View File

@ -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> {

View File

@ -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);
}
}

View File

@ -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,
},
],

View File

@ -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> {

View File

@ -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;
})
);
});
}

View File

@ -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"],