chore: $ and $$ BiDi support (#10318)

This commit is contained in:
Nikolay Vitkov 2023-06-06 14:39:54 +02:00 committed by GitHub
parent dea71bac40
commit 0371beebba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 415 additions and 287 deletions

View File

@ -79,6 +79,9 @@ Suppose we have the markup
</custom-element> </custom-element>
``` ```
> Note: `<template shadowrootmode="open">` is not supported on Firefox.
> You can read more about it [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template#attributes).
Then `custom-element >>> h2` will return `h2`, but `custom-element >>>> h2` will return nothing since the inner `h2` is in a deeper shadow root. Then `custom-element >>> h2` will return `h2`, but `custom-element >>>> h2` will return nothing since the inner `h2` is in a deeper shadow root.
### `P`-elements ### `P`-elements

View File

@ -18,6 +18,7 @@ import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../common/Connection.js'; import {CDPSession} from '../common/Connection.js';
import {ExecutionContext} from '../common/ExecutionContext.js'; import {ExecutionContext} from '../common/ExecutionContext.js';
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
import {MouseClickOptions} from '../common/Input.js'; import {MouseClickOptions} from '../common/Input.js';
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import { import {
@ -28,6 +29,9 @@ import {
NodeFor, NodeFor,
} from '../common/types.js'; } from '../common/types.js';
import {KeyInput} from '../common/USKeyboardLayout.js'; import {KeyInput} from '../common/USKeyboardLayout.js';
import {withSourcePuppeteerURLIfNone} from '../common/util.js';
import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {JSHandle} from './JSHandle.js'; import {JSHandle} from './JSHandle.js';
@ -276,11 +280,13 @@ export class ElementHandle<
*/ */
async $<Selector extends string>( async $<Selector extends string>(
selector: Selector selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null>; ): Promise<ElementHandle<NodeFor<Selector>> | null> {
async $<Selector extends string>(): Promise<ElementHandle< const {updatedSelector, QueryHandler} =
NodeFor<Selector> getQueryHandlerAndSelector(selector);
> | null> { return (await QueryHandler.queryOne(
throw new Error('Not implemented'); this,
updatedSelector
)) as ElementHandle<NodeFor<Selector>> | null;
} }
/** /**
@ -292,11 +298,12 @@ export class ElementHandle<
*/ */
async $$<Selector extends string>( async $$<Selector extends string>(
selector: Selector selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>>; ): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
async $$<Selector extends string>(): Promise< const {updatedSelector, QueryHandler} =
Array<ElementHandle<NodeFor<Selector>>> getQueryHandlerAndSelector(selector);
> { return AsyncIterableUtil.collect(
throw new Error('Not implemented'); QueryHandler.queryAll(this, updatedSelector)
) as Promise<Array<ElementHandle<NodeFor<Selector>>>>;
} }
/** /**
@ -336,9 +343,17 @@ export class ElementHandle<
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>> {
async $eval(): Promise<unknown> { pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
throw new Error('Not implemented'); const elementHandle = await this.$(selector);
if (!elementHandle) {
throw new Error(
`Error: failed to find element matching selector "${selector}"`
);
}
const result = await elementHandle.evaluate(pageFunction, ...args);
await elementHandle.dispose();
return result;
} }
/** /**
@ -385,9 +400,20 @@ export class ElementHandle<
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>> {
async $$eval(): Promise<unknown> { pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
throw new Error('Not implemented'); const results = await this.$$(selector);
const elements = await this.evaluateHandle((_, ...elements) => {
return elements;
}, ...results);
const [result] = await Promise.all([
elements.evaluate(pageFunction, ...args),
...results.map(results => {
return results.dispose();
}),
]);
await elements.dispose();
return result;
} }
/** /**
@ -914,4 +940,11 @@ export class ElementHandle<
return element.ownerSVGElement!; return element.ownerSVGElement!;
}); });
} }
/**
* @internal
*/
assertElementHasWorld(): asserts this {
assert(this.executionContext()._world);
}
} }

View File

@ -57,6 +57,7 @@ import {
isNumber, isNumber,
isString, isString,
waitForEvent, waitForEvent,
withSourcePuppeteerURLIfNone,
} from '../common/util.js'; } from '../common/util.js';
import type {WebWorker} from '../common/WebWorker.js'; import type {WebWorker} from '../common/WebWorker.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
@ -840,11 +841,8 @@ export class Page extends EventEmitter {
*/ */
async $<Selector extends string>( async $<Selector extends string>(
selector: Selector selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null>; ): Promise<ElementHandle<NodeFor<Selector>> | null> {
async $<Selector extends string>(): Promise<ElementHandle< return this.mainFrame().$(selector);
NodeFor<Selector>
> | null> {
throw new Error('Not implemented');
} }
/** /**
@ -856,11 +854,8 @@ export class Page extends EventEmitter {
*/ */
async $$<Selector extends string>( async $$<Selector extends string>(
selector: Selector selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>>; ): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
async $$<Selector extends string>(): Promise< return this.mainFrame().$$(selector);
Array<ElementHandle<NodeFor<Selector>>>
> {
throw new Error('Not implemented');
} }
/** /**
@ -1037,9 +1032,9 @@ export class Page extends EventEmitter {
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>> {
async $eval(): Promise<unknown> { pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
throw new Error('Not implemented'); return this.mainFrame().$eval(selector, pageFunction, ...args);
} }
/** /**
@ -1115,9 +1110,9 @@ export class Page extends EventEmitter {
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>> {
async $$eval(): Promise<unknown> { pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
throw new Error('Not implemented'); return this.mainFrame().$$eval(selector, pageFunction, ...args);
} }
/** /**

View File

@ -27,7 +27,6 @@ import {
} from '../api/ElementHandle.js'; } from '../api/ElementHandle.js';
import {Page, ScreenshotOptions} from '../api/Page.js'; import {Page, ScreenshotOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
@ -39,9 +38,9 @@ import {PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {CDPJSHandle} from './JSHandle.js'; import {CDPJSHandle} from './JSHandle.js';
import {LazyArg} from './LazyArg.js'; import {LazyArg} from './LazyArg.js';
import {CDPPage} from './Page.js'; import {CDPPage} from './Page.js';
import {ElementFor, EvaluateFuncWith, HandleFor, NodeFor} from './types.js'; import {ElementFor, HandleFor, NodeFor} from './types.js';
import {KeyInput} from './USKeyboardLayout.js'; import {KeyInput} from './USKeyboardLayout.js';
import {debugError, isString, withSourcePuppeteerURLIfNone} from './util.js'; import {debugError, isString} from './util.js';
const applyOffsetsToQuad = ( const applyOffsetsToQuad = (
quad: Point[], quad: Point[],
@ -108,73 +107,17 @@ export class CDPElementHandle<
override async $<Selector extends string>( override async $<Selector extends string>(
selector: Selector selector: Selector
): Promise<CDPElementHandle<NodeFor<Selector>> | null> { ): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, QueryHandler} = return super.$(selector) as Promise<CDPElementHandle<
getQueryHandlerAndSelector(selector); NodeFor<Selector>
return (await QueryHandler.queryOne( > | null>;
this,
updatedSelector
)) as CDPElementHandle<NodeFor<Selector>> | null;
} }
override async $$<Selector extends string>( override async $$<Selector extends string>(
selector: Selector selector: Selector
): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> { ): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> {
const {updatedSelector, QueryHandler} = return super.$$(selector) as Promise<
getQueryHandlerAndSelector(selector); Array<CDPElementHandle<NodeFor<Selector>>>
return AsyncIterableUtil.collect( >;
QueryHandler.queryAll(this, updatedSelector)
) as Promise<Array<CDPElementHandle<NodeFor<Selector>>>>;
}
override async $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
const elementHandle = await this.$(selector);
if (!elementHandle) {
throw new Error(
`Error: failed to find element matching selector "${selector}"`
);
}
const result = await elementHandle.evaluate(pageFunction, ...args);
await elementHandle.dispose();
return result;
}
override async $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
const results = await this.$$(selector);
const elements = await this.evaluateHandle((_, ...elements) => {
return elements;
}, ...results);
const [result] = await Promise.all([
elements.evaluate(pageFunction, ...args),
...results.map(results => {
return results.dispose();
}),
]);
await elements.dispose();
return result;
} }
override async $x( override async $x(

View File

@ -14,26 +14,34 @@
* limitations under the License. * limitations under the License.
*/ */
import {ExecutionContext} from './ExecutionContext.js'; import {JSHandle} from '../api/JSHandle.js';
import PuppeteerUtil from '../injected/injected.js';
/** /**
* @internal * @internal
*/ */
export class LazyArg<T> { export interface PuppeteerUtilWrapper {
puppeteerUtil: Promise<JSHandle<PuppeteerUtil>>;
}
/**
* @internal
*/
export class LazyArg<T, Context = PuppeteerUtilWrapper> {
static create = <T>( static create = <T>(
get: (context: ExecutionContext) => Promise<T> | T get: (context: PuppeteerUtilWrapper) => Promise<T> | T
): T => { ): T => {
// We don't want to introduce LazyArg to the type system, otherwise we would // We don't want to introduce LazyArg to the type system, otherwise we would
// have to make it public. // have to make it public.
return new LazyArg(get) as unknown as T; return new LazyArg(get) as unknown as T;
}; };
#get: (context: ExecutionContext) => Promise<T> | T; #get: (context: Context) => Promise<T> | T;
private constructor(get: (context: ExecutionContext) => Promise<T> | T) { private constructor(get: (context: Context) => Promise<T> | T) {
this.#get = get; this.#get = get;
} }
async get(context: ExecutionContext): Promise<T> { async get(context: Context): Promise<T> {
return this.#get(context); return this.#get(context);
} }
} }

View File

@ -76,13 +76,7 @@ import {TargetManagerEmittedEvents} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js'; import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js'; import {Tracing} from './Tracing.js';
import { import {BindingPayload, EvaluateFunc, HandleFor, NodeFor} from './types.js';
BindingPayload,
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
NodeFor,
} from './types.js';
import { import {
createClientError, createClientError,
createJSHandle, createJSHandle,
@ -521,18 +515,6 @@ export class CDPPage extends Page {
return this.#timeoutSettings.timeout(); return this.#timeoutSettings.timeout();
} }
override async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
return this.mainFrame().$(selector);
}
override async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return this.mainFrame().$$(selector);
}
override async evaluateHandle< override async evaluateHandle<
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params> Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
@ -563,38 +545,6 @@ export class CDPPage extends Page {
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>; return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
} }
override async $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
return this.mainFrame().$eval(selector, pageFunction, ...args);
}
override async $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
return this.mainFrame().$$eval(selector, pageFunction, ...args);
}
override async $x(expression: string): Promise<Array<ElementHandle<Node>>> { override async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
return this.mainFrame().$x(expression); return this.mainFrame().$x(expression);
} }

View File

@ -17,7 +17,6 @@
import {ElementHandle} from '../api/ElementHandle.js'; import {ElementHandle} from '../api/ElementHandle.js';
import type {Frame} from '../api/Frame.js'; import type {Frame} from '../api/Frame.js';
import type PuppeteerUtil from '../injected/injected.js'; import type PuppeteerUtil from '../injected/injected.js';
import {assert} from '../util/assert.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {interpolateFunction, stringifyFunction} from '../util/Function.js'; import {interpolateFunction, stringifyFunction} from '../util/Function.js';
@ -108,8 +107,7 @@ export class QueryHandler {
element: ElementHandle<Node>, element: ElementHandle<Node>,
selector: string selector: string
): AwaitableIterable<ElementHandle<Node>> { ): AwaitableIterable<ElementHandle<Node>> {
const world = element.executionContext()._world; element.assertElementHasWorld();
assert(world);
const handle = await element.evaluateHandle( const handle = await element.evaluateHandle(
this._querySelectorAll, this._querySelectorAll,
selector, selector,
@ -129,8 +127,7 @@ export class QueryHandler {
element: ElementHandle<Node>, element: ElementHandle<Node>,
selector: string selector: string
): Promise<ElementHandle<Node> | null> { ): Promise<ElementHandle<Node> | null> {
const world = element.executionContext()._world; element.assertElementHasWorld();
assert(world);
const result = await element.evaluateHandle( const result = await element.evaluateHandle(
this._querySelector, this._querySelector,
selector, selector,

View File

@ -2,11 +2,13 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
import {WaitForOptions} from '../../api/Page.js'; import {WaitForOptions} from '../../api/Page.js';
import PuppeteerUtil from '../../injected/injected.js';
import {assert} from '../../util/assert.js'; import {assert} from '../../util/assert.js';
import {stringifyFunction} from '../../util/Function.js'; import {stringifyFunction} from '../../util/Function.js';
import {ProtocolError, TimeoutError} from '../Errors.js'; import {ProtocolError, TimeoutError} from '../Errors.js';
import {EventEmitter} from '../EventEmitter.js'; import {EventEmitter} from '../EventEmitter.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {scriptInjector} from '../ScriptInjector.js';
import {TimeoutSettings} from '../TimeoutSettings.js'; import {TimeoutSettings} from '../TimeoutSettings.js';
import {EvaluateFunc, HandleFor} from '../types.js'; import {EvaluateFunc, HandleFor} from '../types.js';
import { import {
@ -69,6 +71,22 @@ export class BrowsingContext extends EventEmitter {
this.#id = info.context; this.#id = info.context;
} }
#puppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
const promise = Promise.resolve() as Promise<unknown>;
scriptInjector.inject(script => {
if (this.#puppeteerUtil) {
void this.#puppeteerUtil.then(handle => {
void handle.dispose();
});
}
this.#puppeteerUtil = promise.then(() => {
return this.evaluateHandle(script) as Promise<JSHandle<PuppeteerUtil>>;
});
}, !this.#puppeteerUtil);
return this.#puppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
}
get url(): string { get url(): string {
return this.#url; return this.#url;
} }

View File

@ -47,4 +47,12 @@ export class ElementHandle<
remoteValue(): Bidi.CommonDataTypes.RemoteValue { remoteValue(): Bidi.CommonDataTypes.RemoteValue {
return this.handle.remoteValue(); return this.handle.remoteValue();
} }
/**
* @internal
*/
override assertElementHasWorld(): asserts this {
// TODO: Should assert element has a Sandbox
return;
}
} }

View File

@ -14,13 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
import {ElementHandle} from '../../api/ElementHandle.js';
import {Frame as BaseFrame} from '../../api/Frame.js'; import {Frame as BaseFrame} from '../../api/Frame.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {EvaluateFunc, HandleFor} from '../types.js'; import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from '../types.js';
import {withSourcePuppeteerURLIfNone} from '../util.js';
import {BrowsingContext} from './BrowsingContext.js'; import {BrowsingContext} from './BrowsingContext.js';
import {HTTPResponse} from './HTTPResponse.js'; import {HTTPResponse} from './HTTPResponse.js';
import {Page} from './Page.js'; import {Page} from './Page.js';
import {
MAIN_SANDBOX,
PUPPETEER_SANDBOX,
SandboxChart,
Sandbox,
} from './Sandbox.js';
/** /**
* Puppeteer's Frame class could be viewed as a BiDi BrowsingContext implementation * Puppeteer's Frame class could be viewed as a BiDi BrowsingContext implementation
@ -29,6 +37,7 @@ import {Page} from './Page.js';
export class Frame extends BaseFrame { export class Frame extends BaseFrame {
#page: Page; #page: Page;
#context: BrowsingContext; #context: BrowsingContext;
sandboxes: SandboxChart;
override _id: string; override _id: string;
constructor(page: Page, context: BrowsingContext, parentId?: string | null) { constructor(page: Page, context: BrowsingContext, parentId?: string | null) {
@ -37,6 +46,11 @@ export class Frame extends BaseFrame {
this.#context = context; this.#context = context;
this._id = this.#context.id; this._id = this.#context.id;
this._parentId = parentId ?? undefined; this._parentId = parentId ?? undefined;
this.sandboxes = {
[MAIN_SANDBOX]: new Sandbox(context),
[PUPPETEER_SANDBOX]: new Sandbox(context),
};
} }
override page(): Page { override page(): Page {
@ -119,6 +133,50 @@ export class Frame extends BaseFrame {
return this.#context; return this.#context;
} }
override $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
return this.sandboxes[MAIN_SANDBOX].$(selector);
}
override $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return this.sandboxes[MAIN_SANDBOX].$$(selector);
}
override $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>
>(
selector: Selector,
pageFunction: string | Func,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
return this.sandboxes[MAIN_SANDBOX].$eval(selector, pageFunction, ...args);
}
override $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>(
selector: Selector,
pageFunction: string | Func,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
return this.sandboxes[MAIN_SANDBOX].$$eval(selector, pageFunction, ...args);
}
dispose(): void { dispose(): void {
this.#context.dispose(); this.#context.dispose();
} }

View File

@ -91,7 +91,14 @@ export class JSHandle<T = unknown> extends BaseJSHandle<T> {
// TODO(lightning00blade): Either include return of depth Handles in RemoteValue // TODO(lightning00blade): Either include return of depth Handles in RemoteValue
// or new BiDi command that returns array of remote value // or new BiDi command that returns array of remote value
const keys = await this.evaluate(object => { const keys = await this.evaluate(object => {
return Object.getOwnPropertyNames(object); const enumerableKeys = [];
const descriptors = Object.getOwnPropertyDescriptors(object);
for (const key in descriptors) {
if (descriptors[key]?.enumerable) {
enumerableKeys.push(key);
}
}
return enumerableKeys;
}); });
const map: Map<string, BaseJSHandle> = new Map(); const map: Map<string, BaseJSHandle> = new Map();
const results = await Promise.all( const results = await Promise.all(

View File

@ -0,0 +1,115 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ElementHandle} from '../../api/ElementHandle.js';
import {withSourcePuppeteerURLIfNone} from '../common.js';
import {EvaluateFuncWith, NodeFor} from '../types.js';
import {BrowsingContext} from './BrowsingContext.js';
/**
* A unique key for {@link SandboxChart} to denote the default world.
* Realms are automatically created in the default sandbox.
*
* @internal
*/
export const MAIN_SANDBOX = Symbol('mainSandbox');
/**
* A unique key for {@link SandboxChart} to denote the puppeteer sandbox.
* This world contains all puppeteer-internal bindings/code.
*
* @internal
*/
export const PUPPETEER_SANDBOX = Symbol('puppeteerSandbox');
/**
* @internal
*/
export interface SandboxChart {
[key: string]: Sandbox;
[MAIN_SANDBOX]: Sandbox;
[PUPPETEER_SANDBOX]: Sandbox;
}
/**
* @internal
*/
export class Sandbox {
#document?: ElementHandle<Document>;
#context: BrowsingContext;
constructor(context: BrowsingContext) {
this.#context = context;
}
async document(): Promise<ElementHandle<Document>> {
if (this.#document) {
return this.#document;
}
this.#document = await this.#context.evaluateHandle(() => {
return document;
});
return this.#document;
}
async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const document = await this.document();
return document.$(selector);
}
async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
const document = await this.document();
return document.$$(selector);
}
async $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
const document = await this.document();
return document.$eval(selector, pageFunction, ...args);
}
async $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
const document = await this.document();
return document.$$eval(selector, pageFunction, ...args);
}
}

View File

@ -16,6 +16,7 @@
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {LazyArg} from '../LazyArg.js';
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js'; import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
import {BrowsingContext} from './BrowsingContext.js'; import {BrowsingContext} from './BrowsingContext.js';
@ -141,11 +142,15 @@ export class BidiSerializer {
} }
} }
static serialize( static async serialize(
arg: unknown, arg: unknown,
context: BrowsingContext context: BrowsingContext
): Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue { ): Promise<
// TODO: See use case of LazyArgs Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue
> {
if (arg instanceof LazyArg) {
arg = await arg.get(context);
}
const objectHandle = const objectHandle =
arg && (arg instanceof JSHandle || arg instanceof ElementHandle) arg && (arg instanceof JSHandle || arg instanceof ElementHandle)
? arg ? arg
@ -205,6 +210,7 @@ export class BidiSerializer {
}, {}); }, {});
} }
break; break;
case 'map': case 'map':
return result.value.reduce((acc: Map<unknown, unknown>, tuple) => { return result.value.reduce((acc: Map<unknown, unknown>, tuple) => {
const {key, value} = BidiSerializer.deserializeTuple(tuple); const {key, value} = BidiSerializer.deserializeTuple(tuple);

View File

@ -21,13 +21,13 @@
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.removeScriptToEvaluateOnNewDocument *", "testIdPattern": "[evaluation.spec] Evaluation specs Page.removeScriptToEvaluateOnNewDocument *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[EventEmitter.spec] EventEmitter *", "testIdPattern": "[EventEmitter.spec] EventEmitter *",
@ -51,25 +51,25 @@
"testIdPattern": "[navigation.spec] navigation Frame.goto *", "testIdPattern": "[navigation.spec] navigation Frame.goto *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation *", "testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[navigation.spec] navigation Page.goBack *", "testIdPattern": "[navigation.spec] navigation Page.goBack *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation *", "testIdPattern": "[navigation.spec] navigation Page.waitForNavigation *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[network.spec] network *", "testIdPattern": "[network.spec] network *",
@ -87,7 +87,7 @@
"testIdPattern": "[network.spec] network Page.authenticate *", "testIdPattern": "[network.spec] network Page.authenticate *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *",
@ -201,7 +201,7 @@
"testIdPattern": "[queryhandler.spec] *", "testIdPattern": "[queryhandler.spec] *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL", "SKIP"] "expectations": ["PASS"]
}, },
{ {
"testIdPattern": "[accessibility.spec] *", "testIdPattern": "[accessibility.spec] *",
@ -251,89 +251,41 @@
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL"]
}, },
{
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have correct execution contexts",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts", "testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should accept element handle as an argument",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should be able to throw a tricky error",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should evaluate in the page context",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if elementHandles are from other frames", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if elementHandles are from other frames",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if underlying element was disposed",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw when evaluation triggers reload",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work from-inside an exposed function", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work from-inside an exposed function",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work right after framenavigated", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work right after framenavigated",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *", "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *",
@ -495,7 +447,7 @@
"testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie", "testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[network.spec] network raw network headers Same-origin set-cookie navigation", "testIdPattern": "[network.spec] network raw network headers Same-origin set-cookie navigation",
@ -569,30 +521,6 @@
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["PASS"] "expectations": ["PASS"]
}, },
{
"testIdPattern": "[page.spec] Page Page.setContent should work with accents",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.setContent should work with emojis",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.setContent should work with newline",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.setContent should work with tricky content",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{ {
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests", "testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
@ -605,6 +533,36 @@
"parameters": ["cdp", "firefox"], "parameters": ["cdp", "firefox"],
"expectations": ["SKIP"] "expectations": ["SKIP"]
}, },
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with name and role",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with role",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work with :hover",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests Text selectors in Page should clear caches",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{ {
"testIdPattern": "[requestinterception-experimental.spec] *", "testIdPattern": "[requestinterception-experimental.spec] *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
@ -1577,12 +1535,6 @@
"parameters": ["cdp", "firefox"], "parameters": ["cdp", "firefox"],
"expectations": ["SKIP"] "expectations": ["SKIP"]
}, },
{
"testIdPattern": "[network.spec] network Page.authenticate should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["SKIP"]
},
{ {
"testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders should work", "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders should work",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
@ -2137,18 +2089,12 @@
}, },
{ {
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with name and role", "testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with name and role",
"platforms": ["linux"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"], "parameters": ["cdp", "firefox"],
"expectations": ["FAIL"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with role", "testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors with role",
"platforms": ["linux"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work with deep combinators",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"], "parameters": ["cdp", "firefox"],
"expectations": ["FAIL"] "expectations": ["FAIL"]
@ -2536,5 +2482,11 @@
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "chrome", "headless"], "parameters": ["cdp", "chrome", "headless"],
"expectations": ["FAIL", "PASS"] "expectations": ["FAIL", "PASS"]
},
{
"testIdPattern": "[navigation.spec] navigation \"after each\" hook for \"should work with both domcontentloaded and load\"",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
} }
] ]

View File

@ -1,13 +1,14 @@
<div id="a">hello <button id="b">world</button> <div id="a">hello <button id="b">world</button>
<span id="f"></span> <span id="f"></span>
<div id="c"> <div id="c"></div>
<template shadowrootmode="open"> </div>
shadow dom
<div id="d"> <script>
<template shadowrootmode="open"> const topShadow = document.querySelector('#c');
<a id="e">deep text</a> topShadow.attachShadow({ mode: "open" });
</template> topShadow.shadowRoot.innerHTML = `shadow dom<div id="d"></div>`;
</div>
</template> const innerShadow = topShadow.shadowRoot.querySelector('#d');
</div> innerShadow.attachShadow({ mode: "open" });
</div> innerShadow.shadowRoot.innerHTML = `<a id="e">deep text</a>`;
</script>

View File

@ -17,7 +17,7 @@
import {ServerResponse} from 'http'; import {ServerResponse} from 'http';
import expect from 'expect'; import expect from 'expect';
import {TimeoutError} from 'puppeteer'; import {Frame, TimeoutError} from 'puppeteer';
import {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js'; import {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js';
import { import {
@ -595,18 +595,30 @@ describe('navigation', function () {
server.setRoute('/one-style.css', (_req, res) => { server.setRoute('/one-style.css', (_req, res) => {
return (response = res); return (response = res);
}); });
const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); let error: Error | undefined;
const domContentLoadedPromise = page.waitForNavigation({
waitUntil: 'domcontentloaded',
});
let bothFired = false; let bothFired = false;
const navigationPromise = page
.goto(server.PREFIX + '/one-style.html')
.catch(_error => {
return (error = _error);
});
const domContentLoadedPromise = page
.waitForNavigation({
waitUntil: 'domcontentloaded',
})
.catch(_error => {
return (error = _error);
});
const bothFiredPromise = page const bothFiredPromise = page
.waitForNavigation({ .waitForNavigation({
waitUntil: ['load', 'domcontentloaded'], waitUntil: ['load', 'domcontentloaded'],
}) })
.then(() => { .then(() => {
return (bothFired = true); return (bothFired = true);
})
.catch(_error => {
return (error = _error);
}); });
await server.waitForRequest('/one-style.css').catch(() => {}); await server.waitForRequest('/one-style.css').catch(() => {});
@ -615,6 +627,7 @@ describe('navigation', function () {
response.end(); response.end();
await bothFiredPromise; await bothFiredPromise;
await navigationPromise; await navigationPromise;
expect(error).toBeUndefined();
}); });
it('should work with clicking on anchor links', async () => { it('should work with clicking on anchor links', async () => {
const {page, server} = getTestState(); const {page, server} = getTestState();
@ -690,19 +703,40 @@ describe('navigation', function () {
expect(forwardResponse).toBe(null); expect(forwardResponse).toBe(null);
expect(page.url()).toBe(server.PREFIX + '/second.html'); expect(page.url()).toBe(server.PREFIX + '/second.html');
}); });
it('should work when subframe issues window.stop()', async () => { it('should work when subframe issues window.stop()', async function () {
const {page, server} = getTestState(); const {page, server} = getTestState();
server.setRoute('/frames/style.css', () => {}); server.setRoute('/frames/style.css', () => {});
let frame: Frame | undefined;
let timeout: NodeJS.Timeout | undefined;
const eventPromises = Promise.race([
Promise.all([
waitEvent(page, 'frameattached').then(_frame => {
return (frame = _frame);
}),
waitEvent(page, 'framenavigated', f => {
return f === frame;
}),
]),
new Promise((_, rej) => {
timeout = setTimeout(() => {
return rej(new Error('Timeout'));
}, this.timeout());
}),
]);
const navigationPromise = page.goto( const navigationPromise = page.goto(
server.PREFIX + '/frames/one-frame.html' server.PREFIX + '/frames/one-frame.html'
); );
const frame = await waitEvent(page, 'frameattached'); try {
await waitEvent(page, 'framenavigated', f => { await eventPromises;
return f === frame; clearTimeout(timeout);
}); } catch (error) {
navigationPromise.catch(() => {});
throw error;
}
await Promise.all([ await Promise.all([
frame.evaluate(() => { frame!.evaluate(() => {
return window.stop(); return window.stop();
}), }),
navigationPromise, navigationPromise,