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>
```
> 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.
### `P`-elements

View File

@ -18,6 +18,7 @@ import {Protocol} from 'devtools-protocol';
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 {
@ -28,6 +29,9 @@ import {
NodeFor,
} from '../common/types.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 {JSHandle} from './JSHandle.js';
@ -276,11 +280,13 @@ export class ElementHandle<
*/
async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null>;
async $<Selector extends string>(): Promise<ElementHandle<
NodeFor<Selector>
> | null> {
throw new Error('Not implemented');
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector);
return (await QueryHandler.queryOne(
this,
updatedSelector
)) as ElementHandle<NodeFor<Selector>> | null;
}
/**
@ -292,11 +298,12 @@ export class ElementHandle<
*/
async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>>;
async $$<Selector extends string>(): Promise<
Array<ElementHandle<NodeFor<Selector>>>
> {
throw new Error('Not implemented');
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector);
return AsyncIterableUtil.collect(
QueryHandler.queryAll(this, updatedSelector)
) as Promise<Array<ElementHandle<NodeFor<Selector>>>>;
}
/**
@ -336,9 +343,17 @@ export class ElementHandle<
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async $eval(): Promise<unknown> {
throw new Error('Not implemented');
): 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;
}
/**
@ -385,9 +400,20 @@ export class ElementHandle<
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async $$eval(): Promise<unknown> {
throw new Error('Not implemented');
): 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;
}
/**
@ -914,4 +940,11 @@ export class ElementHandle<
return element.ownerSVGElement!;
});
}
/**
* @internal
*/
assertElementHasWorld(): asserts this {
assert(this.executionContext()._world);
}
}

View File

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

View File

@ -27,7 +27,6 @@ import {
} from '../api/ElementHandle.js';
import {Page, ScreenshotOptions} from '../api/Page.js';
import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js';
@ -39,9 +38,9 @@ import {PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {CDPJSHandle} from './JSHandle.js';
import {LazyArg} from './LazyArg.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 {debugError, isString, withSourcePuppeteerURLIfNone} from './util.js';
import {debugError, isString} from './util.js';
const applyOffsetsToQuad = (
quad: Point[],
@ -108,73 +107,17 @@ export class CDPElementHandle<
override async $<Selector extends string>(
selector: Selector
): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector);
return (await QueryHandler.queryOne(
this,
updatedSelector
)) as CDPElementHandle<NodeFor<Selector>> | null;
return super.$(selector) as Promise<CDPElementHandle<
NodeFor<Selector>
> | null>;
}
override async $$<Selector extends string>(
selector: Selector
): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> {
const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(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;
return super.$$(selector) as Promise<
Array<CDPElementHandle<NodeFor<Selector>>>
>;
}
override async $x(

View File

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

View File

@ -76,13 +76,7 @@ import {TargetManagerEmittedEvents} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js';
import {
BindingPayload,
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
NodeFor,
} from './types.js';
import {BindingPayload, EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {
createClientError,
createJSHandle,
@ -521,18 +515,6 @@ export class CDPPage extends Page {
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<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
@ -563,38 +545,6 @@ export class CDPPage extends Page {
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>>> {
return this.mainFrame().$x(expression);
}

View File

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

View File

@ -47,4 +47,12 @@ export class ElementHandle<
remoteValue(): Bidi.CommonDataTypes.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.
*/
import {ElementHandle} from '../../api/ElementHandle.js';
import {Frame as BaseFrame} from '../../api/Frame.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 {HTTPResponse} from './HTTPResponse.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
@ -29,6 +37,7 @@ import {Page} from './Page.js';
export class Frame extends BaseFrame {
#page: Page;
#context: BrowsingContext;
sandboxes: SandboxChart;
override _id: string;
constructor(page: Page, context: BrowsingContext, parentId?: string | null) {
@ -37,6 +46,11 @@ export class Frame extends BaseFrame {
this.#context = context;
this._id = this.#context.id;
this._parentId = parentId ?? undefined;
this.sandboxes = {
[MAIN_SANDBOX]: new Sandbox(context),
[PUPPETEER_SANDBOX]: new Sandbox(context),
};
}
override page(): Page {
@ -119,6 +133,50 @@ export class Frame extends BaseFrame {
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 {
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
// or new BiDi command that returns array of remote value
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 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 {LazyArg} from '../LazyArg.js';
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
import {BrowsingContext} from './BrowsingContext.js';
@ -141,11 +142,15 @@ export class BidiSerializer {
}
}
static serialize(
static async serialize(
arg: unknown,
context: BrowsingContext
): Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue {
// TODO: See use case of LazyArgs
): Promise<
Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue
> {
if (arg instanceof LazyArg) {
arg = await arg.get(context);
}
const objectHandle =
arg && (arg instanceof JSHandle || arg instanceof ElementHandle)
? arg
@ -205,6 +210,7 @@ export class BidiSerializer {
}, {});
}
break;
case 'map':
return result.value.reduce((acc: Map<unknown, unknown>, tuple) => {
const {key, value} = BidiSerializer.deserializeTuple(tuple);

View File

@ -21,13 +21,13 @@
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.removeScriptToEvaluateOnNewDocument *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[EventEmitter.spec] EventEmitter *",
@ -51,25 +51,25 @@
"testIdPattern": "[navigation.spec] navigation Frame.goto *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[navigation.spec] navigation Page.goBack *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[network.spec] network *",
@ -87,7 +87,7 @@
"testIdPattern": "[network.spec] network Page.authenticate *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[network.spec] network Page.setBypassServiceWorker *",
@ -201,7 +201,7 @@
"testIdPattern": "[queryhandler.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL", "SKIP"]
"expectations": ["PASS"]
},
{
"testIdPattern": "[accessibility.spec] *",
@ -251,89 +251,41 @@
"parameters": ["webDriverBiDi"],
"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",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"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"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if elementHandles are from other frames",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"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"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work from-inside an exposed function",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work right after framenavigated",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument *",
@ -495,7 +447,7 @@
"testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["SKIP"]
"expectations": ["FAIL"]
},
{
"testIdPattern": "[network.spec] network raw network headers Same-origin set-cookie navigation",
@ -569,30 +521,6 @@
"parameters": ["webDriverBiDi"],
"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",
"platforms": ["darwin", "linux", "win32"],
@ -605,6 +533,36 @@
"parameters": ["cdp", "firefox"],
"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] *",
"platforms": ["darwin", "linux", "win32"],
@ -1577,12 +1535,6 @@
"parameters": ["cdp", "firefox"],
"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",
"platforms": ["darwin", "linux", "win32"],
@ -2137,18 +2089,12 @@
},
{
"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"],
"expectations": ["FAIL"]
},
{
"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"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
@ -2536,5 +2482,11 @@
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "chrome", "headless"],
"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>
<span id="f"></span>
<div id="c">
<template shadowrootmode="open">
shadow dom
<div id="d">
<template shadowrootmode="open">
<a id="e">deep text</a>
</template>
</div>
</template>
</div>
</div>
<div id="c"></div>
</div>
<script>
const topShadow = document.querySelector('#c');
topShadow.attachShadow({ mode: "open" });
topShadow.shadowRoot.innerHTML = `shadow dom<div id="d"></div>`;
const innerShadow = topShadow.shadowRoot.querySelector('#d');
innerShadow.attachShadow({ mode: "open" });
innerShadow.shadowRoot.innerHTML = `<a id="e">deep text</a>`;
</script>

View File

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