mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: $ and $$ BiDi support (#10318)
This commit is contained in:
parent
dea71bac40
commit
0371beebba
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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(
|
||||
|
115
packages/puppeteer-core/src/common/bidi/Sandbox.ts
Normal file
115
packages/puppeteer-core/src/common/bidi/Sandbox.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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"]
|
||||
}
|
||||
]
|
||||
|
@ -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>
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user