chore: add waitForSelector for BiDi (#10383)

This commit is contained in:
Nikolay Vitkov 2023-06-16 09:16:04 +02:00 committed by GitHub
parent 866addd132
commit d560299aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 795 additions and 391 deletions

16
package-lock.json generated
View File

@ -3085,9 +3085,9 @@
"license": "ISC"
},
"node_modules/chromium-bidi": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.11.tgz",
"integrity": "sha512-p03ajLhlQ5gebw3cmbDBFmBc2wnJM5dnXS8Phu6mblGn/KQd76yOVL5VwE0VAisa7oazNfKGTaXlIZ8Q5Bb9OA==",
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.12.tgz",
"integrity": "sha512-yl0ngMHtYUGJa2G0lkcbPvbnUZ9WMQyMNSfYmlrGD1nHRNyI9KOGw3dOaofFugXHHToneUaSmF9iUdgCBamCjA==",
"dependencies": {
"mitt": "3.0.0"
},
@ -10164,7 +10164,7 @@
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "1.4.1",
"chromium-bidi": "0.4.11",
"chromium-bidi": "0.4.12",
"cross-fetch": "3.1.6",
"debug": "4.3.4",
"devtools-protocol": "0.0.1135028",
@ -12317,9 +12317,9 @@
"version": "1.1.4"
},
"chromium-bidi": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.11.tgz",
"integrity": "sha512-p03ajLhlQ5gebw3cmbDBFmBc2wnJM5dnXS8Phu6mblGn/KQd76yOVL5VwE0VAisa7oazNfKGTaXlIZ8Q5Bb9OA==",
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.12.tgz",
"integrity": "sha512-yl0ngMHtYUGJa2G0lkcbPvbnUZ9WMQyMNSfYmlrGD1nHRNyI9KOGw3dOaofFugXHHToneUaSmF9iUdgCBamCjA==",
"requires": {
"mitt": "3.0.0"
}
@ -15738,7 +15738,7 @@
"version": "file:packages/puppeteer-core",
"requires": {
"@puppeteer/browsers": "1.4.1",
"chromium-bidi": "0.4.11",
"chromium-bidi": "0.4.12",
"cross-fetch": "3.1.6",
"debug": "4.3.4",
"devtools-protocol": "0.0.1135028",

View File

@ -132,7 +132,7 @@
"author": "The Chromium Authors",
"license": "Apache-2.0",
"dependencies": {
"chromium-bidi": "0.4.11",
"chromium-bidi": "0.4.12",
"cross-fetch": "3.1.6",
"debug": "4.3.4",
"devtools-protocol": "0.0.1135028",

View File

@ -16,11 +16,13 @@
import {Protocol} from 'devtools-protocol';
import {Frame} from '../api/Frame.js';
import {CDPSession} from '../common/Connection.js';
import {ExecutionContext} from '../common/ExecutionContext.js';
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
import {MouseClickOptions} from '../common/Input.js';
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import {LazyArg} from '../common/LazyArg.js';
import {
ElementFor,
EvaluateFuncWith,
@ -33,7 +35,6 @@ import {isString, withSourcePuppeteerURLIfNone} from '../common/util.js';
import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {Frame} from './Frame.js';
import {JSHandle} from './JSHandle.js';
import {ScreenshotOptions} from './Page.js';
@ -485,12 +486,30 @@ export class ElementHandle<
)) as ElementHandle<NodeFor<Selector>> | null;
}
async #checkVisibility(visibility: boolean): Promise<boolean> {
const element = await this.frame.isolatedRealm().adoptHandle(this);
try {
return await this.frame.isolatedRealm().evaluate(
async (PuppeteerUtil, element, visibility) => {
return Boolean(PuppeteerUtil.checkVisibility(element, visibility));
},
LazyArg.create(context => {
return context.puppeteerUtil;
}),
element,
visibility
);
} finally {
await element.dispose();
}
}
/**
* Checks if an element is visible using the same mechanism as
* {@link ElementHandle.waitForSelector}.
*/
async isVisible(): Promise<boolean> {
throw new Error('Not implemented.');
return this.#checkVisibility(true);
}
/**
@ -498,7 +517,7 @@ export class ElementHandle<
* {@link ElementHandle.waitForSelector}.
*/
async isHidden(): Promise<boolean> {
throw new Error('Not implemented.');
return this.#checkVisibility(false);
}
/**
@ -565,14 +584,16 @@ export class ElementHandle<
*/
async waitForXPath(
xpath: string,
options?: {
options: {
visible?: boolean;
hidden?: boolean;
timeout?: number;
} = {}
): Promise<ElementHandle<Node> | null> {
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
): Promise<ElementHandle<Node> | null>;
async waitForXPath(): Promise<ElementHandle<Node> | null> {
throw new Error('Not implemented');
return this.waitForSelector(`xpath/${xpath}`, options);
}
/**

View File

@ -20,6 +20,7 @@ import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {CDPSession} from '../common/Connection.js';
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
import {ExecutionContext} from '../common/ExecutionContext.js';
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
import {
IsolatedWorldChart,
WaitForSelectorOptions,
@ -29,11 +30,52 @@ import {
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
InnerLazyParams,
NodeFor,
} from '../common/types.js';
import {TaskManager} from '../common/WaitTask.js';
import {JSHandle} from './JSHandle.js';
import {Locator} from './Locator.js';
/**
* @internal
*/
export interface Realm {
taskManager: TaskManager;
waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
InnerLazyParams<Params>
>
>(
pageFunction: Func | string,
options: {
polling?: 'raf' | 'mutation' | number;
timeout?: number;
root?: ElementHandle<Node>;
signal?: AbortSignal;
},
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T>;
transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T>;
evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
}
/**
* @public
*/
@ -307,6 +349,20 @@ export class Frame {
throw new Error('Not implemented');
}
/**
* @internal
*/
mainRealm(): Realm {
throw new Error('Not implemented');
}
/**
* @internal
*/
isolatedRealm(): Realm {
throw new Error('Not implemented');
}
/**
* Behaves identically to {@link Page.evaluateHandle} except it's run within
* the context of this frame.
@ -529,12 +585,15 @@ export class Frame {
*/
async waitForSelector<Selector extends string>(
selector: Selector,
options?: WaitForSelectorOptions
): Promise<ElementHandle<NodeFor<Selector>> | null>;
async waitForSelector<Selector extends string>(): Promise<ElementHandle<
NodeFor<Selector>
> | null> {
throw new Error('Not implemented');
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector);
return (await QueryHandler.waitFor(
this,
updatedSelector,
options
)) as ElementHandle<NodeFor<Selector>> | null;
}
/**
@ -561,10 +620,12 @@ export class Frame {
*/
async waitForXPath(
xpath: string,
options?: WaitForSelectorOptions
): Promise<ElementHandle<Node> | null>;
async waitForXPath(): Promise<ElementHandle<Node> | null> {
throw new Error('Not implemented');
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<Node> | null> {
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
return this.waitForSelector(`xpath/${xpath}`, options);
}
/**
@ -605,16 +666,15 @@ export class Frame {
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
options?: FrameWaitForFunctionOptions,
options: FrameWaitForFunctionOptions = {},
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
throw new Error('Not implemented');
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return this.mainRealm().waitForFunction(
pageFunction,
options,
...args
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
}
/**
* The full HTML contents of the frame, including the DOCTYPE.
*/

View File

@ -33,9 +33,7 @@ import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js';
import {PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {CDPJSHandle} from './JSHandle.js';
import {LazyArg} from './LazyArg.js';
import {CDPPage} from './Page.js';
import {NodeFor} from './types.js';
import {KeyInput} from './USKeyboardLayout.js';
@ -128,46 +126,6 @@ export class CDPElementHandle<
> | null;
}
override async waitForXPath(
xpath: string,
options: {
visible?: boolean;
hidden?: boolean;
timeout?: number;
} = {}
): Promise<CDPElementHandle<Node> | null> {
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
return this.waitForSelector(`xpath/${xpath}`, options);
}
async #checkVisibility(visibility: boolean): Promise<boolean> {
const element = await this.frame.worlds[PUPPETEER_WORLD].adoptHandle(this);
try {
return await this.frame.worlds[PUPPETEER_WORLD].evaluate(
async (PuppeteerUtil, element, visibility) => {
return Boolean(PuppeteerUtil.checkVisibility(element, visibility));
},
LazyArg.create(context => {
return context.puppeteerUtil;
}),
element,
visibility
);
} finally {
await element.dispose();
}
}
override async isVisible(): Promise<boolean> {
return this.#checkVisibility(true);
}
override async isHidden(): Promise<boolean> {
return this.#checkVisibility(false);
}
override async contentFrame(): Promise<Frame | null> {
const nodeInfo = await this.client.send('DOM.describeNode', {
objectId: this.id,

View File

@ -21,7 +21,6 @@ import {
Frame as BaseFrame,
FrameAddScriptTagOptions,
FrameAddStyleTagOptions,
FrameWaitForFunctionOptions,
} from '../api/Frame.js';
import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js';
@ -36,12 +35,7 @@ import {
} from './DeviceRequestPrompt.js';
import {ExecutionContext} from './ExecutionContext.js';
import {FrameManager} from './FrameManager.js';
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
import {
IsolatedWorld,
IsolatedWorldChart,
WaitForSelectorOptions,
} from './IsolatedWorld.js';
import {IsolatedWorld, IsolatedWorldChart} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
@ -221,6 +215,20 @@ export class Frame extends BaseFrame {
return this.worlds[MAIN_WORLD].executionContext();
}
/**
* @internal
*/
override mainRealm(): IsolatedWorld {
return this.worlds[MAIN_WORLD];
}
/**
* @internal
*/
override isolatedRealm(): IsolatedWorld {
return this.worlds[PUPPETEER_WORLD];
}
override async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
@ -232,7 +240,7 @@ export class Frame extends BaseFrame {
this.evaluateHandle.name,
pageFunction
);
return this.worlds[MAIN_WORLD].evaluateHandle(pageFunction, ...args);
return this.mainRealm().evaluateHandle(pageFunction, ...args);
}
override async evaluate<
@ -246,19 +254,19 @@ export class Frame extends BaseFrame {
this.evaluate.name,
pageFunction
);
return this.worlds[MAIN_WORLD].evaluate(pageFunction, ...args);
return this.mainRealm().evaluate(pageFunction, ...args);
}
override async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
return this.worlds[MAIN_WORLD].$(selector);
return this.mainRealm().$(selector);
}
override async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return this.worlds[MAIN_WORLD].$$(selector);
return this.mainRealm().$$(selector);
}
override async $eval<
@ -274,7 +282,7 @@ export class Frame extends BaseFrame {
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
return this.worlds[MAIN_WORLD].$eval(selector, pageFunction, ...args);
return this.mainRealm().$eval(selector, pageFunction, ...args);
}
override async $$eval<
@ -290,53 +298,15 @@ export class Frame extends BaseFrame {
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
return this.worlds[MAIN_WORLD].$$eval(selector, pageFunction, ...args);
return this.mainRealm().$$eval(selector, pageFunction, ...args);
}
override async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
return this.worlds[MAIN_WORLD].$x(expression);
}
override async waitForSelector<Selector extends string>(
selector: Selector,
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector);
return (await QueryHandler.waitFor(
this,
updatedSelector,
options
)) as ElementHandle<NodeFor<Selector>> | null;
}
override async waitForXPath(
xpath: string,
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<Node> | null> {
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
return this.waitForSelector(`xpath/${xpath}`, options);
}
override waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
options: FrameWaitForFunctionOptions = {},
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return this.worlds[MAIN_WORLD].waitForFunction(
pageFunction,
options,
...args
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
return this.mainRealm().$x(expression);
}
override async content(): Promise<string> {
return this.worlds[PUPPETEER_WORLD].content();
return this.isolatedRealm().content();
}
override async setContent(
@ -346,7 +316,7 @@ export class Frame extends BaseFrame {
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} = {}
): Promise<void> {
return this.worlds[PUPPETEER_WORLD].setContent(html, options);
return this.isolatedRealm().setContent(html, options);
}
override name(): string {
@ -388,8 +358,8 @@ export class Frame extends BaseFrame {
type = type ?? 'text/javascript';
return this.worlds[MAIN_WORLD].transferHandle(
await this.worlds[PUPPETEER_WORLD].evaluateHandle(
return this.mainRealm().transferHandle(
await this.isolatedRealm().evaluateHandle(
async ({Deferred}, {url, id, type, content}) => {
const deferred = Deferred.create<void>();
const script = document.createElement('script');
@ -456,8 +426,8 @@ export class Frame extends BaseFrame {
options.content = content;
}
return this.worlds[MAIN_WORLD].transferHandle(
await this.worlds[PUPPETEER_WORLD].evaluateHandle(
return this.mainRealm().transferHandle(
await this.isolatedRealm().evaluateHandle(
async ({Deferred}, {url, content}) => {
const deferred = Deferred.create<void>();
let element: HTMLStyleElement | HTMLLinkElement;
@ -504,23 +474,23 @@ export class Frame extends BaseFrame {
selector: string,
options: Readonly<ClickOptions> = {}
): Promise<void> {
return this.worlds[PUPPETEER_WORLD].click(selector, options);
return this.isolatedRealm().click(selector, options);
}
override async focus(selector: string): Promise<void> {
return this.worlds[PUPPETEER_WORLD].focus(selector);
return this.isolatedRealm().focus(selector);
}
override async hover(selector: string): Promise<void> {
return this.worlds[PUPPETEER_WORLD].hover(selector);
return this.isolatedRealm().hover(selector);
}
override select(selector: string, ...values: string[]): Promise<string[]> {
return this.worlds[PUPPETEER_WORLD].select(selector, ...values);
return this.isolatedRealm().select(selector, ...values);
}
override async tap(selector: string): Promise<void> {
return this.worlds[PUPPETEER_WORLD].tap(selector);
return this.isolatedRealm().tap(selector);
}
override async type(
@ -528,11 +498,11 @@ export class Frame extends BaseFrame {
text: string,
options?: {delay: number}
): Promise<void> {
return this.worlds[PUPPETEER_WORLD].type(selector, text, options);
return this.isolatedRealm().type(selector, text, options);
}
override async title(): Promise<string> {
return this.worlds[PUPPETEER_WORLD].title();
return this.isolatedRealm().title();
}
_deviceRequestPromptManager(): DeviceRequestPromptManager {

View File

@ -34,7 +34,10 @@ import {Target} from './Target.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {debugError, PuppeteerURL} from './util.js';
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
/**
* @internal
*/
export const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
/**
* We use symbols to prevent external parties listening to these events.

View File

@ -17,6 +17,7 @@
import {Protocol} from 'devtools-protocol';
import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {Realm} from '../api/Frame.js';
import {JSHandle} from '../api/JSHandle.js';
import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js';
@ -99,7 +100,7 @@ export interface IsolatedWorldChart {
/**
* @internal
*/
export class IsolatedWorld {
export class IsolatedWorld implements Realm {
#frame: Frame;
#document?: ElementHandle<Document>;
#context = Deferred.create<ExecutionContext>();

View File

@ -22,7 +22,6 @@ import {interpolateFunction, stringifyFunction} from '../util/Function.js';
import {transposeIterableHandle} from './HandleIterator.js';
import type {WaitForSelectorOptions} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js';
import type {Awaitable, AwaitableIterable} from './types.js';
@ -160,7 +159,7 @@ export class QueryHandler {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame);
element = await frame.isolatedRealm().adoptHandle(elementOrFrame);
}
const {visible = false, hidden = false, timeout, signal} = options;
@ -168,7 +167,7 @@ export class QueryHandler {
try {
signal?.throwIfAborted();
const handle = await frame.worlds[PUPPETEER_WORLD].waitForFunction(
const handle = await frame.isolatedRealm().waitForFunction(
async (PuppeteerUtil, query, selector, root, visible) => {
const querySelector = PuppeteerUtil.createFunction(
query
@ -204,7 +203,7 @@ export class QueryHandler {
await handle.dispose();
return null;
}
return frame.worlds[MAIN_WORLD].transferHandle(handle);
return frame.mainRealm().transferHandle(handle);
} catch (error) {
if (!isErrorLike(error)) {
throw error;

View File

@ -15,6 +15,7 @@
*/
import {ElementHandle} from '../api/ElementHandle.js';
import {Realm} from '../api/Frame.js';
import {JSHandle} from '../api/JSHandle.js';
import type {Poller} from '../injected/Poller.js';
import {Deferred} from '../util/Deferred.js';
@ -22,7 +23,6 @@ import {isErrorLike} from '../util/ErrorLike.js';
import {stringifyFunction} from '../util/Function.js';
import {TimeoutError} from './Errors.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {LazyArg} from './LazyArg.js';
import {HandleFor} from './types.js';
@ -40,7 +40,7 @@ export interface WaitTaskOptions {
* @internal
*/
export class WaitTask<T = unknown> {
#world: IsolatedWorld;
#world: Realm;
#polling: 'raf' | 'mutation' | number;
#root?: ElementHandle<Node>;
@ -55,7 +55,7 @@ export class WaitTask<T = unknown> {
#signal?: AbortSignal;
constructor(
world: IsolatedWorld,
world: Realm,
options: WaitTaskOptions,
fn: ((...args: unknown[]) => Promise<T>) | string,
...args: unknown[]

View File

@ -2,37 +2,17 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
import {WaitForOptions} from '../../api/Page.js';
import PuppeteerUtil from '../../injected/injected.js';
import {assert} from '../../util/assert.js';
import {Deferred} from '../../util/Deferred.js';
import {stringifyFunction} from '../../util/Function.js';
import type {CDPSession, Connection as CDPConnection} from '../Connection.js';
import {ProtocolError, TimeoutError} from '../Errors.js';
import {EventEmitter} from '../EventEmitter.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {scriptInjector} from '../ScriptInjector.js';
import {TimeoutSettings} from '../TimeoutSettings.js';
import {EvaluateFunc, HandleFor} from '../types.js';
import {
PuppeteerURL,
getPageContent,
getSourcePuppeteerURLIfAvailable,
isString,
setPageContent,
waitWithTimeout,
} from '../util.js';
import {getPageContent, setPageContent, waitWithTimeout} from '../util.js';
import {Connection} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {JSHandle} from './JSHandle.js';
import {BidiSerializer} from './Serializer.js';
import {createEvaluationError} from './utils.js';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
const getSourceUrlComment = (url: string) => {
return `//# sourceURL=${url}`;
};
import {Realm} from './Realm.js';
/**
* @internal
@ -104,8 +84,7 @@ export class CDPSessionWrapper extends EventEmitter implements CDPSession {
/**
* @internal
*/
export class BrowsingContext extends EventEmitter {
connection: Connection;
export class BrowsingContext extends Realm {
#timeoutSettings: TimeoutSettings;
#id: string;
#url = 'about:blank';
@ -116,27 +95,15 @@ export class BrowsingContext extends EventEmitter {
timeoutSettings: TimeoutSettings,
info: Bidi.BrowsingContext.Info
) {
super();
super(connection, info.context);
this.connection = connection;
this.#timeoutSettings = timeoutSettings;
this.#id = info.context;
this.#cdpSession = new CDPSessionWrapper(this);
}
#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>>;
createSandboxRealm(sandbox: string): Realm {
return new Realm(this.connection, this.#id, sandbox);
}
get url(): string {
@ -212,97 +179,6 @@ export class BrowsingContext extends EventEmitter {
);
}
async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return this.#evaluate(false, pageFunction, ...args);
}
async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
return this.#evaluate(true, pageFunction, ...args);
}
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: true,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: false,
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: boolean,
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
const sourceUrlComment = getSourceUrlComment(
getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ??
PuppeteerURL.INTERNAL_URL
);
let responsePromise;
const resultOwnership = returnByValue ? 'none' : 'root';
if (isString(pageFunction)) {
const expression = SOURCE_URL_REGEX.test(pageFunction)
? pageFunction
: `${pageFunction}\n${sourceUrlComment}\n`;
responsePromise = this.connection.send('script.evaluate', {
expression,
target: {context: this.#id},
resultOwnership,
awaitPromise: true,
});
} else {
let functionDeclaration = stringifyFunction(pageFunction);
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
? functionDeclaration
: `${functionDeclaration}\n${sourceUrlComment}\n`;
responsePromise = this.connection.send('script.callFunction', {
functionDeclaration,
arguments: await Promise.all(
args.map(arg => {
return BidiSerializer.serialize(arg, this);
})
),
target: {context: this.#id},
resultOwnership,
awaitPromise: true,
});
}
const {result} = await responsePromise;
if ('type' in result && result.type === 'exception') {
throw createEvaluationError(result.exceptionDetails);
}
return returnByValue
? BidiSerializer.deserialize(result.result)
: getBidiHandle(this, result.result);
}
async setContent(
html: string,
options: {
@ -344,29 +220,16 @@ export class BrowsingContext extends EventEmitter {
return this.#cdpSession.send(method, ...paramArgs);
}
dispose(): void {
this.removeAllListeners();
this.connection.unregisterBrowsingContexts(this.#id);
}
title(): Promise<string> {
return this.evaluate(() => {
return document.title;
});
}
}
/**
* @internal
*/
export function getBidiHandle(
context: BrowsingContext,
result: Bidi.CommonDataTypes.RemoteValue
): JSHandle | ElementHandle<Node> {
if (result.type === 'node' || result.type === 'window') {
return new ElementHandle(context, result);
dispose(): void {
this.removeAllListeners();
this.connection.unregisterBrowsingContexts(this.#id);
}
return new JSHandle(context, result);
}
/**

View File

@ -65,6 +65,10 @@ interface Commands {
params: Bidi.Script.DisownParameters;
returnType: Bidi.Script.DisownResult;
};
'script.addPreloadScript': {
params: Bidi.Script.AddPreloadScriptParameters;
returnType: Bidi.Script.AddPreloadScriptResult;
};
'browsingContext.create': {
params: Bidi.BrowsingContext.CreateParameters;
@ -116,11 +120,11 @@ interface Commands {
};
'session.subscribe': {
params: Bidi.Session.SubscriptionRequest;
returnType: Bidi.Session.SubscribeResult;
returnType: Bidi.Message.EmptyResult;
};
'session.unsubscribe': {
params: Bidi.Session.SubscriptionRequest;
returnType: Bidi.Session.UnsubscribeResult;
returnType: Bidi.Message.EmptyResult;
};
'cdp.sendCommand': {
params: Bidi.CDP.SendCommandParams;

View File

@ -18,8 +18,9 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {ElementHandle as BaseElementHandle} from '../../api/ElementHandle.js';
import {BrowsingContext} from './BrowsingContext.js';
import {Frame} from './Frame.js';
import {JSHandle} from './JSHandle.js';
import {Realm} from './Realm.js';
/**
* @internal
@ -28,15 +29,22 @@ export class ElementHandle<
ElementType extends Node = Element
> extends BaseElementHandle<ElementType> {
declare handle: JSHandle<ElementType>;
#frame: Frame;
constructor(
context: BrowsingContext,
remoteValue: Bidi.CommonDataTypes.RemoteValue
realm: Realm,
remoteValue: Bidi.CommonDataTypes.RemoteValue,
frame: Frame
) {
super(new JSHandle(context, remoteValue));
super(new JSHandle(realm, remoteValue));
this.#frame = frame;
}
context(): BrowsingContext {
override get frame(): Frame {
return this.#frame;
}
context(): Realm {
return this.handle.context();
}

View File

@ -16,7 +16,9 @@
import {ElementHandle} from '../../api/ElementHandle.js';
import {Frame as BaseFrame} from '../../api/Frame.js';
import {UTILITY_WORLD_NAME} from '../FrameManager.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {TimeoutSettings} from '../TimeoutSettings.js';
import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from '../types.js';
import {withSourcePuppeteerURLIfNone} from '../util.js';
@ -40,7 +42,12 @@ export class Frame extends BaseFrame {
sandboxes: SandboxChart;
override _id: string;
constructor(page: Page, context: BrowsingContext, parentId?: string | null) {
constructor(
page: Page,
context: BrowsingContext,
timeoutSettings: TimeoutSettings,
parentId?: string | null
) {
super();
this.#page = page;
this.#context = context;
@ -48,11 +55,22 @@ export class Frame extends BaseFrame {
this._parentId = parentId ?? undefined;
this.sandboxes = {
[MAIN_SANDBOX]: new Sandbox(context),
[PUPPETEER_SANDBOX]: new Sandbox(context),
[MAIN_SANDBOX]: new Sandbox(context, timeoutSettings),
[PUPPETEER_SANDBOX]: new Sandbox(
context.createSandboxRealm(UTILITY_WORLD_NAME),
timeoutSettings
),
};
}
override mainRealm(): Sandbox {
return this.sandboxes[MAIN_SANDBOX];
}
override isolatedRealm(): Sandbox {
return this.sandboxes[PUPPETEER_SANDBOX];
}
override page(): Page {
return this.#page;
}

View File

@ -21,26 +21,23 @@ import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js';
import {EvaluateFuncWith, HandleFor, HandleOr} from '../../common/types.js';
import {withSourcePuppeteerURLIfNone} from '../util.js';
import {BrowsingContext} from './BrowsingContext.js';
import {Realm} from './Realm.js';
import {BidiSerializer} from './Serializer.js';
import {releaseReference} from './utils.js';
export class JSHandle<T = unknown> extends BaseJSHandle<T> {
#disposed = false;
#context;
#realm: Realm;
#remoteValue;
constructor(
context: BrowsingContext,
remoteValue: Bidi.CommonDataTypes.RemoteValue
) {
constructor(realm: Realm, remoteValue: Bidi.CommonDataTypes.RemoteValue) {
super();
this.#context = context;
this.#realm = realm;
this.#remoteValue = remoteValue;
}
context(): BrowsingContext {
return this.#context;
context(): Realm {
return this.#realm;
}
override get disposed(): boolean {
@ -136,7 +133,7 @@ export class JSHandle<T = unknown> extends BaseJSHandle<T> {
}
this.#disposed = true;
if ('handle' in this.#remoteValue) {
await releaseReference(this.#context, this.#remoteValue);
await releaseReference(this.#realm, this.#remoteValue);
}
}

View File

@ -53,12 +53,13 @@ import {
import {Browser} from './Browser.js';
import {BrowserContext} from './BrowserContext.js';
import {BrowsingContext, getBidiHandle} from './BrowsingContext.js';
import {BrowsingContext} from './BrowsingContext.js';
import {Connection} from './Connection.js';
import {Frame} from './Frame.js';
import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js';
import {NetworkManager} from './NetworkManager.js';
import {getBidiHandle} from './Realm.js';
import {BidiSerializer} from './Serializer.js';
/**
@ -208,7 +209,13 @@ export class Page extends PageBase {
);
this.#connection.registerBrowsingContexts(context);
const frame = new Frame(this, context, info.parent);
const frame = new Frame(
this,
context,
this.#timeoutSettings,
info.parent
);
context.setFrame(frame);
this.#frameTree.addFrame(frame);
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
@ -250,12 +257,13 @@ export class Page extends PageBase {
}
#onLogEntryAdded(event: Bidi.Log.LogEntry): void {
if (!this.frame(event.source.context)) {
const frame = this.frame(event.source.context);
if (!frame) {
return;
}
if (isConsoleLogEntry(event)) {
const args = event.args.map(arg => {
return getBidiHandle(this.mainFrame().context(), arg);
return getBidiHandle(frame.context(), arg, frame);
});
const text = args

View File

@ -0,0 +1,171 @@
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import PuppeteerUtil from '../../injected/injected.js';
import {stringifyFunction} from '../../util/Function.js';
import {EventEmitter} from '../EventEmitter.js';
import {scriptInjector} from '../ScriptInjector.js';
import {EvaluateFunc, HandleFor} from '../types.js';
import {
PuppeteerURL,
getSourcePuppeteerURLIfAvailable,
isString,
} from '../util.js';
import {Connection} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {Frame} from './Frame.js';
import {JSHandle} from './JSHandle.js';
import {BidiSerializer} from './Serializer.js';
import {createEvaluationError} from './utils.js';
export const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
export const getSourceUrlComment = (url: string): string => {
return `//# sourceURL=${url}`;
};
export class Realm extends EventEmitter {
connection: Connection;
#frame!: Frame;
#id: string;
#sandbox?: string;
constructor(connection: Connection, id: string, sandbox?: string) {
super();
this.connection = connection;
this.#id = id;
this.#sandbox = sandbox;
}
get target(): Bidi.Script.Target {
return {
context: this.#id,
sandbox: this.#sandbox,
};
}
setFrame(frame: Frame): void {
this.#frame = frame;
}
protected internalPuppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
const promise = Promise.resolve() as Promise<unknown>;
scriptInjector.inject(script => {
if (this.internalPuppeteerUtil) {
void this.internalPuppeteerUtil.then(handle => {
void handle.dispose();
});
}
this.internalPuppeteerUtil = promise.then(() => {
return this.evaluateHandle(script) as Promise<JSHandle<PuppeteerUtil>>;
});
}, !this.internalPuppeteerUtil);
return this.internalPuppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
}
async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return this.#evaluate(false, pageFunction, ...args);
}
async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
return this.#evaluate(true, pageFunction, ...args);
}
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: true,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: false,
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
async #evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
returnByValue: boolean,
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
const sourceUrlComment = getSourceUrlComment(
getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ??
PuppeteerURL.INTERNAL_URL
);
let responsePromise;
const resultOwnership = returnByValue ? 'none' : 'root';
if (isString(pageFunction)) {
const expression = SOURCE_URL_REGEX.test(pageFunction)
? pageFunction
: `${pageFunction}\n${sourceUrlComment}\n`;
responsePromise = this.connection.send('script.evaluate', {
expression,
target: this.target,
resultOwnership,
awaitPromise: true,
});
} else {
let functionDeclaration = stringifyFunction(pageFunction);
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
? functionDeclaration
: `${functionDeclaration}\n${sourceUrlComment}\n`;
responsePromise = this.connection.send('script.callFunction', {
functionDeclaration,
arguments: await Promise.all(
args.map(arg => {
return BidiSerializer.serialize(arg, this as any);
})
),
target: this.target,
resultOwnership,
awaitPromise: true,
});
}
const {result} = await responsePromise;
if ('type' in result && result.type === 'exception') {
throw createEvaluationError(result.exceptionDetails);
}
return returnByValue
? BidiSerializer.deserialize(result.result)
: getBidiHandle(this as any, result.result, this.#frame);
}
}
/**
* @internal
*/
export function getBidiHandle(
realmOrContext: Realm,
result: Bidi.CommonDataTypes.RemoteValue,
frame: Frame
): JSHandle | ElementHandle<Node> {
if (result.type === 'node' || result.type === 'window') {
return new ElementHandle(realmOrContext, result, frame);
}
return new JSHandle(realmOrContext, result);
}

View File

@ -15,11 +15,21 @@
*/
import {ElementHandle} from '../../api/ElementHandle.js';
import {withSourcePuppeteerURLIfNone} from '../common.js';
import {EvaluateFuncWith, NodeFor} from '../types.js';
import {BrowsingContext} from './BrowsingContext.js';
import {Realm as RealmBase} from '../../api/Frame.js';
import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js';
import {TimeoutSettings} from '../TimeoutSettings.js';
import {
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
InnerLazyParams,
NodeFor,
} from '../types.js';
import {withSourcePuppeteerURLIfNone} from '../util.js';
import {TaskManager, WaitTask} from '../WaitTask.js';
import {JSHandle} from './JSHandle.js';
import {Realm} from './Realm.js';
/**
* A unique key for {@link SandboxChart} to denote the default world.
* Realms are automatically created in the default sandbox.
@ -47,19 +57,27 @@ export interface SandboxChart {
/**
* @internal
*/
export class Sandbox {
export class Sandbox implements RealmBase {
#document?: ElementHandle<Document>;
#context: BrowsingContext;
#realm: Realm;
constructor(context: BrowsingContext) {
this.#context = context;
#timeoutSettings: TimeoutSettings;
#taskManager = new TaskManager();
constructor(context: Realm, timeoutSettings: TimeoutSettings) {
this.#realm = context;
this.#timeoutSettings = timeoutSettings;
}
get taskManager(): TaskManager {
return this.#taskManager;
}
async document(): Promise<ElementHandle<Document>> {
if (this.#document) {
return this.#document;
}
this.#document = await this.#context.evaluateHandle(() => {
this.#document = await this.#realm.evaluateHandle(() => {
return document;
});
return this.#document;
@ -117,4 +135,90 @@ export class Sandbox {
const document = await this.document();
return document.$x(expression);
}
async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluateHandle.name,
pageFunction
);
return this.#realm.evaluateHandle(pageFunction, ...args);
}
async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluate.name,
pageFunction
);
return this.#realm.evaluate(pageFunction, ...args);
}
async adoptHandle<T extends BaseJSHandle<Node>>(handle: T): Promise<T> {
return (await this.evaluateHandle(node => {
return node;
}, handle)) as unknown as T;
}
async transferHandle<T extends BaseJSHandle<Node>>(handle: T): Promise<T> {
if ((handle as unknown as JSHandle).context() === this.#realm) {
return handle;
}
const transferredHandle = await this.evaluateHandle(node => {
return node;
}, handle);
await handle.dispose();
return transferredHandle as unknown as T;
}
waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
InnerLazyParams<Params>
>
>(
pageFunction: Func | string,
options: {
polling?: 'raf' | 'mutation' | number;
timeout?: number;
root?: ElementHandle<Node>;
signal?: AbortSignal;
} = {},
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
const {
polling = 'raf',
timeout = this.#timeoutSettings.timeout(),
root,
signal,
} = options;
if (typeof polling === 'number' && polling < 0) {
throw new Error('Cannot poll with non-positive interval');
}
const waitTask = new WaitTask(
this,
{
polling,
root,
timeout,
signal,
},
pageFunction as unknown as
| ((...args: unknown[]) => Promise<Awaited<ReturnType<Func>>>)
| string,
...args
);
return waitTask.result;
}
}

View File

@ -156,7 +156,10 @@ export class BidiSerializer {
? arg
: null;
if (objectHandle) {
if (objectHandle.context() !== context) {
if (
objectHandle.context() !== context &&
!('sharedId' in objectHandle.remoteValue())
) {
throw new Error(
'JSHandles can be evaluated only in the context they were created!'
);

View File

@ -19,7 +19,7 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {debug} from '../Debug.js';
import {PuppeteerURL} from '../util.js';
import {BrowsingContext} from './BrowsingContext.js';
import {Realm} from './Realm.js';
import {BidiSerializer} from './Serializer.js';
/**
@ -30,7 +30,7 @@ export const debugError = debug('puppeteer:error');
* @internal
*/
export async function releaseReference(
client: BrowsingContext,
client: Realm,
remoteReference: Bidi.CommonDataTypes.RemoteReference
): Promise<void> {
if (!remoteReference.handle) {
@ -38,7 +38,7 @@ export async function releaseReference(
}
await client.connection
.send('script.disown', {
target: {context: client.id},
target: client.target,
handles: [remoteReference.handle],
})
.catch((error: any) => {

View File

@ -263,6 +263,24 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should have an error message specifically for awaiting an element to be hidden",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should have correct stack trace for timeout",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should respect timeout",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[browser.spec] Browser specs Browser.process should return child_process instance",
"platforms": ["darwin", "linux", "win32"],
@ -275,6 +293,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |pipe| option should fire \"disconnected\" when closing with pipe",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[Connection.spec] WebDriver BiDi Connection should work",
"platforms": ["darwin", "linux", "win32"],
@ -311,35 +335,35 @@
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitFor",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitForSelector",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitForSelector on an element",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Element.toElement should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Element.waitForSelector should wait correctly with waitForSelector on an element",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs Element.waitForXPath should wait correctly with waitForXPath on an element",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.isVisible and ElementHandle.isHidden should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
"expectations": ["PASS"]
},
{
"testIdPattern": "[emulation.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts",
@ -773,6 +797,12 @@
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[page.spec] Page Page.url should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests",
"platforms": ["darwin", "linux", "win32"],
@ -807,7 +837,7 @@
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work for ARIA selectors in multiple isolated worlds",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work with :hover",
@ -875,6 +905,120 @@
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should be cancellable",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should have an error message specifically for awaiting an element to be hidden",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should have correct stack trace for timeout",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should immediately resolve promise if node exists",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should resolve promise when node is added",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should respect timeout",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should respond to node attribute mutation",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should return null if waiting to hide non-existing element",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should return the element handle",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (bounding box)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (display)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (removal)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (visibility)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (bounding box)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (display)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (visibility)",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible recursively",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should work when node is added through innerHTML",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should work with removed MutationObserver",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[waittask.spec] waittask specs Frame.waitForTimeout waits for the given timeout before resolving",
"platforms": ["darwin", "linux", "win32"],
@ -971,12 +1115,30 @@
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should be able to connect using browserUrl, with and without trailing slash",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should throw when trying to connect to non-existing browser",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should throw when trying to connect to non-existing browser",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |pipe| option should fire \"disconnected\" when closing with pipe",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle",
"platforms": ["darwin", "linux", "win32"],
@ -1175,18 +1337,42 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulate should support clicking",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulate should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateCPUThrottling should change the CPU throttling rate successfully",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateMediaFeatures should throw in case of bad argument",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateMediaFeatures should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateMediaType should throw in case of bad argument",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateMediaType should work",
"platforms": ["darwin", "linux", "win32"],
@ -1199,6 +1385,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateNetworkConditions should change navigator.connection.effectiveType",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateTimezone should throw for invalid timezone IDs",
"platforms": ["darwin", "linux", "win32"],
@ -1211,12 +1403,24 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateVisionDeficiency should throw for invalid vision deficiencies",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateVisionDeficiency should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.viewport should detect touch when applying viewport with touches",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.viewport should get the proper viewport size",
"platforms": ["darwin", "linux", "win32"],
@ -1229,42 +1433,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.viewport should support touch emulation",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.viewport should detect touch when applying viewport with touches",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulate should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulate should support clicking",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[emulation.spec] Emulation Page.emulateNetworkConditions should change navigator.connection.effectiveType",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs \"after each\" hook for \"should transfer 100Mb of data from page to node.js\"",
"platforms": ["darwin"],
@ -1367,6 +1541,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with mixed content",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception",
"platforms": ["darwin", "linux", "win32"],
@ -1553,12 +1733,36 @@
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[locator.spec] Locator Locator.click can be aborted",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[locator.spec] Locator Locator.click should retry clicks on errors",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[locator.spec] Locator Locator.click should time out",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[locator.spec] Locator Locator.race can be aborted",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[locator.spec] Locator Locator.scroll should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[mouse.spec] Mouse should reset properly",
"platforms": ["darwin", "linux", "win32"],
@ -2177,6 +2381,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.addScriptTag should throw an error if loading from url fail",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page",
"platforms": ["darwin", "linux", "win32"],
@ -2189,6 +2399,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[page.spec] Page Page.addStyleTag should throw an error if loading from url fail",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[page.spec] Page Page.addStyleTag should throw when added with content to the CSP page",
"platforms": ["darwin", "linux", "win32"],
@ -2459,12 +2675,6 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.url should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work",
"platforms": ["darwin", "linux", "win32"],
@ -2783,6 +2993,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[worker.spec] Workers should report errors",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events",
"platforms": ["win32"],

View File

@ -39,7 +39,7 @@ describe('headful tests', function () {
/* These tests fire up an actual browser so let's
* allow a higher timeout
*/
this.timeout(20 * 1000);
this.timeout(20_000);
let headfulOptions: PuppeteerLaunchOptions | undefined;
let headlessOptions: PuppeteerLaunchOptions & {headless: boolean};

View File

@ -35,7 +35,7 @@ import {
import {dumpFrames, waitEvent} from './utils.js';
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
const FIREFOX_TIMEOUT = 30 * 1000;
const FIREFOX_TIMEOUT = 30_000;
describe('Launcher specs', function () {
setupTestBrowserHooks();