chore: implement Frames for BiDi (#10121)
This commit is contained in:
parent
609584a8b8
commit
2808240c71
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -194,7 +194,8 @@ jobs:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
# Disabled as BiDi has issue on mac https://bugzilla.mozilla.org/show_bug.cgi?id=1832778
|
||||
# - macos-latest
|
||||
suite:
|
||||
- firefox-bidi
|
||||
- firefox-headful
|
||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -2710,9 +2710,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
|
||||
"integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
|
||||
"version": "0.4.9",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
|
||||
"integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
|
||||
"dependencies": {
|
||||
"mitt": "3.0.0"
|
||||
},
|
||||
@ -9464,7 +9464,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "1.2.0",
|
||||
"chromium-bidi": "0.4.7",
|
||||
"chromium-bidi": "0.4.9",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1120988",
|
||||
@ -11441,9 +11441,9 @@
|
||||
"version": "1.1.4"
|
||||
},
|
||||
"chromium-bidi": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
|
||||
"integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
|
||||
"version": "0.4.9",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
|
||||
"integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
|
||||
"requires": {
|
||||
"mitt": "3.0.0"
|
||||
}
|
||||
@ -14503,7 +14503,7 @@
|
||||
"version": "file:packages/puppeteer-core",
|
||||
"requires": {
|
||||
"@puppeteer/browsers": "1.2.0",
|
||||
"chromium-bidi": "0.4.7",
|
||||
"chromium-bidi": "0.4.9",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1120988",
|
||||
|
@ -132,7 +132,7 @@
|
||||
"author": "The Chromium Authors",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"chromium-bidi": "0.4.7",
|
||||
"chromium-bidi": "0.4.9",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1120988",
|
||||
|
@ -18,7 +18,6 @@ import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {CDPSession} from '../common/Connection.js';
|
||||
import {ExecutionContext} from '../common/ExecutionContext.js';
|
||||
import {Frame} from '../common/Frame.js';
|
||||
import {MouseClickOptions} from '../common/Input.js';
|
||||
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||
import {
|
||||
@ -30,6 +29,7 @@ import {
|
||||
} from '../common/types.js';
|
||||
import {KeyInput} from '../common/USKeyboardLayout.js';
|
||||
|
||||
import {Frame} from './Frame.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {ScreenshotOptions} from './Page.js';
|
||||
|
||||
|
879
packages/puppeteer-core/src/api/Frame.ts
Normal file
879
packages/puppeteer-core/src/api/Frame.ts
Normal file
@ -0,0 +1,879 @@
|
||||
/**
|
||||
* 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 {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
||||
import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
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 {
|
||||
IsolatedWorldChart,
|
||||
WaitForSelectorOptions,
|
||||
} from '../common/IsolatedWorld.js';
|
||||
import {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
|
||||
import {
|
||||
EvaluateFunc,
|
||||
EvaluateFuncWith,
|
||||
HandleFor,
|
||||
NodeFor,
|
||||
} from '../common/types.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FrameWaitForFunctionOptions {
|
||||
/**
|
||||
* An interval at which the `pageFunction` is executed, defaults to `raf`. If
|
||||
* `polling` is a number, then it is treated as an interval in milliseconds at
|
||||
* which the function would be executed. If `polling` is a string, then it can
|
||||
* be one of the following values:
|
||||
*
|
||||
* - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame`
|
||||
* callback. This is the tightest polling mode which is suitable to observe
|
||||
* styling changes.
|
||||
*
|
||||
* - `mutation` - to execute `pageFunction` on every DOM mutation.
|
||||
*/
|
||||
polling?: 'raf' | 'mutation' | number;
|
||||
/**
|
||||
* Maximum time to wait in milliseconds. Defaults to `30000` (30 seconds).
|
||||
* Pass `0` to disable the timeout. Puppeteer's default timeout can be changed
|
||||
* using {@link Page.setDefaultTimeout}.
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* A signal object that allows you to cancel a waitForFunction call.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FrameAddScriptTagOptions {
|
||||
/**
|
||||
* URL of the script to be added.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* Path to a JavaScript file to be injected into the frame.
|
||||
*
|
||||
* @remarks
|
||||
* If `path` is a relative path, it is resolved relative to the current
|
||||
* working directory (`process.cwd()` in Node.js).
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* JavaScript to be injected into the frame.
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* Sets the `type` of the script. Use `module` in order to load an ES2015 module.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Sets the `id` of the script.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FrameAddStyleTagOptions {
|
||||
/**
|
||||
* the URL of the CSS file to be added.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* The path to a CSS file to be injected into the frame.
|
||||
* @remarks
|
||||
* If `path` is a relative path, it is resolved relative to the current
|
||||
* working directory (`process.cwd()` in Node.js).
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* Raw CSS content to be injected into the frame.
|
||||
*/
|
||||
content?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a DOM frame.
|
||||
*
|
||||
* To understand frames, you can think of frames as `<iframe>` elements. Just
|
||||
* like iframes, frames can be nested, and when JavaScript is executed in a
|
||||
* frame, the JavaScript does not effect frames inside the ambient frame the
|
||||
* JavaScript executes in.
|
||||
*
|
||||
* @example
|
||||
* At any point in time, {@link Page | pages} expose their current frame
|
||||
* tree via the {@link Page.mainFrame} and {@link Frame.childFrames} methods.
|
||||
*
|
||||
* @example
|
||||
* An example of dumping frame tree:
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* const browser = await puppeteer.launch();
|
||||
* const page = await browser.newPage();
|
||||
* await page.goto('https://www.google.com/chrome/browser/canary.html');
|
||||
* dumpFrameTree(page.mainFrame(), '');
|
||||
* await browser.close();
|
||||
*
|
||||
* function dumpFrameTree(frame, indent) {
|
||||
* console.log(indent + frame.url());
|
||||
* for (const child of frame.childFrames()) {
|
||||
* dumpFrameTree(child, indent + ' ');
|
||||
* }
|
||||
* }
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* An example of getting text from an iframe element:
|
||||
*
|
||||
* ```ts
|
||||
* const frame = page.frames().find(frame => frame.name() === 'myframe');
|
||||
* const text = await frame.$eval('.selector', element => element.textContent);
|
||||
* console.log(text);
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* Frame lifecycles are controlled by three events that are all dispatched on
|
||||
* the parent {@link Frame.page | page}:
|
||||
*
|
||||
* - {@link PageEmittedEvents.FrameAttached}
|
||||
* - {@link PageEmittedEvents.FrameNavigated}
|
||||
* - {@link PageEmittedEvents.FrameDetached}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class Frame {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_id!: string;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_parentId?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
worlds!: IsolatedWorldChart;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_name?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_hasStartedLoading = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* The page associated with the frame.
|
||||
*/
|
||||
page(): Page {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `true` if the frame is an out-of-process (OOP) frame. Otherwise,
|
||||
* `false`.
|
||||
*/
|
||||
isOOPFrame(): boolean {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates a frame to the given url.
|
||||
*
|
||||
* @remarks
|
||||
* Navigation to `about:blank` or navigation to the same URL with a different
|
||||
* hash will succeed and return `null`.
|
||||
*
|
||||
* :::warning
|
||||
*
|
||||
* Headless mode doesn't support navigation to a PDF document. See the {@link
|
||||
* https://bugs.chromium.org/p/chromium/issues/detail?id=761295 | upstream
|
||||
* issue}.
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* @param url - the URL to navigate the frame to. This should include the
|
||||
* scheme, e.g. `https://`.
|
||||
* @param options - navigation options. `waitUntil` is useful to define when
|
||||
* the navigation should be considered successful - see the docs for
|
||||
* {@link PuppeteerLifeCycleEvent} for more details.
|
||||
*
|
||||
* @returns A promise which resolves to the main resource response. In case of
|
||||
* multiple redirects, the navigation will resolve with the response of the
|
||||
* last redirect.
|
||||
* @throws This method will throw an error if:
|
||||
*
|
||||
* - there's an SSL error (e.g. in case of self-signed certificates).
|
||||
* - target URL is invalid.
|
||||
* - the `timeout` is exceeded during navigation.
|
||||
* - the remote server does not respond or is unreachable.
|
||||
* - the main resource failed to load.
|
||||
*
|
||||
* This method will not throw an error when any valid HTTP status code is
|
||||
* returned by the remote server, including 404 "Not Found" and 500 "Internal
|
||||
* Server Error". The status code for such responses can be retrieved by
|
||||
* calling {@link HTTPResponse.status}.
|
||||
*/
|
||||
async goto(
|
||||
url: string,
|
||||
options?: {
|
||||
referer?: string;
|
||||
referrerPolicy?: string;
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}
|
||||
): Promise<HTTPResponse | null>;
|
||||
async goto(): Promise<HTTPResponse | null> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the frame to navigate. It is useful for when you run code which
|
||||
* will indirectly cause the frame to navigate.
|
||||
*
|
||||
* Usage of the
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/History_API | History API}
|
||||
* to change the URL is considered a navigation.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const [response] = await Promise.all([
|
||||
* // The navigation promise resolves after navigation has finished
|
||||
* frame.waitForNavigation(),
|
||||
* // Clicking the link will indirectly cause a navigation
|
||||
* frame.click('a.my-link'),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param options - options to configure when the navigation is consided
|
||||
* finished.
|
||||
* @returns a promise that resolves when the frame navigates to a new URL.
|
||||
*/
|
||||
async waitForNavigation(options?: {
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}): Promise<HTTPResponse | null>;
|
||||
async waitForNavigation(): Promise<HTTPResponse | null> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_client(): CDPSession {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
executionContext(): Promise<ExecutionContext> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to {@link Page.evaluateHandle} except it's run within
|
||||
* the context of this frame.
|
||||
*
|
||||
* @see {@link Page.evaluateHandle} for details.
|
||||
*/
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to {@link Page.evaluate} except it's run within the
|
||||
* the context of this frame.
|
||||
*
|
||||
* @see {@link Page.evaluate} for details.
|
||||
*/
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(): Promise<Awaited<ReturnType<Func>>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the frame for an element matching the given selector.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @returns A {@link ElementHandle | element handle} to the first element
|
||||
* matching the given selector. Otherwise, `null`.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the frame for all elements matching the given selector.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @returns An array of {@link ElementHandle | element handles} that point to
|
||||
* elements matching the given selector.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given function on the first element matching the given selector in
|
||||
* the frame.
|
||||
*
|
||||
* If the given function returns a promise, then this method will wait till
|
||||
* the promise resolves.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const searchValue = await frame.$eval('#search', el => el.value);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param pageFunction - The function to be evaluated in the frame's context.
|
||||
* The first element matching the selector will be passed to the function as
|
||||
* its first argument.
|
||||
* @param args - Additional arguments to pass to `pageFunction`.
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
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>>>;
|
||||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(): Promise<Awaited<ReturnType<Func>>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given function on an array of elements matching the given selector
|
||||
* in the frame.
|
||||
*
|
||||
* If the given function returns a promise, then this method will wait till
|
||||
* the promise resolves.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const divsCounts = await frame.$$eval('div', divs => divs.length);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param pageFunction - The function to be evaluated in the frame's context.
|
||||
* An array of elements matching the given selector will be passed to the
|
||||
* function as its first argument.
|
||||
* @param args - Additional arguments to pass to `pageFunction`.
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
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>>>;
|
||||
async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<
|
||||
Array<NodeFor<Selector>>,
|
||||
Params
|
||||
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
|
||||
>(): Promise<Awaited<ReturnType<Func>>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Frame.$$} with the `xpath` prefix.
|
||||
*
|
||||
* Example: `await frame.$$('xpath/' + xpathExpression)`
|
||||
*
|
||||
* This method evaluates the given XPath expression and returns the results.
|
||||
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
|
||||
* automatically.
|
||||
* @param expression - the XPath expression to evaluate.
|
||||
*/
|
||||
async $x(expression: string): Promise<Array<ElementHandle<Node>>>;
|
||||
async $x(): Promise<Array<ElementHandle<Node>>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an element matching the given selector to appear in the frame.
|
||||
*
|
||||
* This method works across navigations.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* const browser = await puppeteer.launch();
|
||||
* const page = await browser.newPage();
|
||||
* let currentURL;
|
||||
* page
|
||||
* .mainFrame()
|
||||
* .waitForSelector('img')
|
||||
* .then(() => console.log('First URL with image: ' + currentURL));
|
||||
*
|
||||
* for (currentURL of [
|
||||
* 'https://example.com',
|
||||
* 'https://google.com',
|
||||
* 'https://bbc.com',
|
||||
* ]) {
|
||||
* await page.goto(currentURL);
|
||||
* }
|
||||
* await browser.close();
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query and wait for.
|
||||
* @param options - Options for customizing waiting behavior.
|
||||
* @returns An element matching the given selector.
|
||||
* @throws Throws if an element matching the given selector doesn't appear.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Frame.waitForSelector} with the `xpath` prefix.
|
||||
*
|
||||
* Example: `await frame.waitForSelector('xpath/' + xpathExpression)`
|
||||
*
|
||||
* The method evaluates the XPath expression relative to the Frame.
|
||||
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
|
||||
* automatically.
|
||||
*
|
||||
* Wait for the `xpath` to appear in page. If at the moment of calling the
|
||||
* method the `xpath` already exists, the method will return immediately. If
|
||||
* the xpath doesn't appear after the `timeout` milliseconds of waiting, the
|
||||
* function will throw.
|
||||
*
|
||||
* For a code example, see the example for {@link Frame.waitForSelector}. That
|
||||
* function behaves identically other than taking a CSS selector rather than
|
||||
* an XPath.
|
||||
*
|
||||
* @param xpath - the XPath expression to wait for.
|
||||
* @param options - options to configure the visibility of the element and how
|
||||
* long to wait before timing out.
|
||||
*/
|
||||
async waitForXPath(
|
||||
xpath: string,
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<Node> | null>;
|
||||
async waitForXPath(): Promise<ElementHandle<Node> | null> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* The `waitForFunction` can be used to observe viewport size change:
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* . const browser = await puppeteer.launch();
|
||||
* . const page = await browser.newPage();
|
||||
* . const watchDog = page.mainFrame().waitForFunction('window.innerWidth < 100');
|
||||
* . page.setViewport({width: 50, height: 50});
|
||||
* . await watchDog;
|
||||
* . await browser.close();
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* To pass arguments from Node.js to the predicate of `page.waitForFunction` function:
|
||||
*
|
||||
* ```ts
|
||||
* const selector = '.foo';
|
||||
* await frame.waitForFunction(
|
||||
* selector => !!document.querySelector(selector),
|
||||
* {}, // empty options object
|
||||
* selector
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction - the function to evaluate in the frame context.
|
||||
* @param options - options to configure the polling method and timeout.
|
||||
* @param args - arguments to pass to the `pageFunction`.
|
||||
* @returns the promise which resolve when the `pageFunction` returns a truthy value.
|
||||
*/
|
||||
waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* The full HTML contents of the frame, including the DOCTYPE.
|
||||
*/
|
||||
async content(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of the frame.
|
||||
*
|
||||
* @param html - HTML markup to assign to the page.
|
||||
* @param options - Options to configure how long before timing out and at
|
||||
* what point to consider the content setting successful.
|
||||
*/
|
||||
async setContent(
|
||||
html: string,
|
||||
options?: {
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}
|
||||
): Promise<void>;
|
||||
async setContent(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's `name` attribute as specified in the tag.
|
||||
*
|
||||
* @remarks
|
||||
* If the name is empty, it returns the `id` attribute instead.
|
||||
*
|
||||
* @remarks
|
||||
* This value is calculated once when the frame is created, and will not
|
||||
* update if the attribute is changed later.
|
||||
*/
|
||||
name(): string {
|
||||
return this._name || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's URL.
|
||||
*/
|
||||
url(): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent frame, if any. Detached and main frames return `null`.
|
||||
*/
|
||||
parentFrame(): Frame | null {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of child frames.
|
||||
*/
|
||||
childFrames(): Frame[] {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is`true` if the frame has been detached. Otherwise, `false`.
|
||||
*/
|
||||
isDetached(): boolean {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a `<script>` tag into the page with the desired url or content.
|
||||
*
|
||||
* @param options - Options for the script.
|
||||
* @returns An {@link ElementHandle | element handle} to the injected
|
||||
* `<script>` element.
|
||||
*/
|
||||
async addScriptTag(
|
||||
options: FrameAddScriptTagOptions
|
||||
): Promise<ElementHandle<HTMLScriptElement>>;
|
||||
async addScriptTag(): Promise<ElementHandle<HTMLScriptElement>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a `<link rel="stylesheet">` tag into the page with the desired URL or
|
||||
* a `<style type="text/css">` tag with the content.
|
||||
*
|
||||
* @returns An {@link ElementHandle | element handle} to the loaded `<link>`
|
||||
* or `<style>` element.
|
||||
*/
|
||||
async addStyleTag(
|
||||
options: Omit<FrameAddStyleTagOptions, 'url'>
|
||||
): Promise<ElementHandle<HTMLStyleElement>>;
|
||||
async addStyleTag(
|
||||
options: FrameAddStyleTagOptions
|
||||
): Promise<ElementHandle<HTMLLinkElement>>;
|
||||
async addStyleTag(): Promise<
|
||||
ElementHandle<HTMLStyleElement | HTMLLinkElement>
|
||||
> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the first element found that matches `selector`.
|
||||
*
|
||||
* @remarks
|
||||
* If `click()` triggers a navigation event and there's a separate
|
||||
* `page.waitForNavigation()` promise to be resolved, you may end up with a
|
||||
* race condition that yields unexpected results. The correct pattern for
|
||||
* click and wait for navigation is the following:
|
||||
*
|
||||
* ```ts
|
||||
* const [response] = await Promise.all([
|
||||
* page.waitForNavigation(waitOptions),
|
||||
* frame.click(selector, clickOptions),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
*/
|
||||
async click(
|
||||
selector: string,
|
||||
options?: Readonly<ClickOptions>
|
||||
): Promise<void>;
|
||||
async click(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the first element that matches the `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async focus(selector: string): Promise<void>;
|
||||
async focus(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovers the pointer over the center of the first element that matches the
|
||||
* `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async hover(selector: string): Promise<void>;
|
||||
async hover(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a set of value on the first `<select>` element that matches the
|
||||
* `selector`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* frame.select('select#colors', 'blue'); // single selection
|
||||
* frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param values - The array of values to select. If the `<select>` has the
|
||||
* `multiple` attribute, all values are considered, otherwise only the first
|
||||
* one is taken into account.
|
||||
* @returns the list of values that were successfully selected.
|
||||
* @throws Throws if there's no `<select>` matching `selector`.
|
||||
*/
|
||||
select(selector: string, ...values: string[]): Promise<string[]>;
|
||||
select(): Promise<string[]> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Taps the first element that matches the `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async tap(selector: string): Promise<void>;
|
||||
async tap(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character
|
||||
* in the text.
|
||||
*
|
||||
* @remarks
|
||||
* To press a special key, like `Control` or `ArrowDown`, use
|
||||
* {@link Keyboard.press}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* await frame.type('#mytextarea', 'Hello'); // Types instantly
|
||||
* await frame.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user
|
||||
* ```
|
||||
*
|
||||
* @param selector - the selector for the element to type into. If there are
|
||||
* multiple the first will be used.
|
||||
* @param text - text to type into the element
|
||||
* @param options - takes one option, `delay`, which sets the time to wait
|
||||
* between key presses in milliseconds. Defaults to `0`.
|
||||
*/
|
||||
async type(
|
||||
selector: string,
|
||||
text: string,
|
||||
options?: {delay: number}
|
||||
): Promise<void>;
|
||||
async type(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replace with `new Promise(r => setTimeout(r, milliseconds));`.
|
||||
*
|
||||
* Causes your script to wait for the given number of milliseconds.
|
||||
*
|
||||
* @remarks
|
||||
* It's generally recommended to not wait for a number of seconds, but instead
|
||||
* use {@link Frame.waitForSelector}, {@link Frame.waitForXPath} or
|
||||
* {@link Frame.waitForFunction} to wait for exactly the conditions you want.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Wait for 1 second:
|
||||
*
|
||||
* ```ts
|
||||
* await frame.waitForTimeout(1000);
|
||||
* ```
|
||||
*
|
||||
* @param milliseconds - the number of milliseconds to wait.
|
||||
*/
|
||||
waitForTimeout(milliseconds: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's title.
|
||||
*/
|
||||
async title(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is typically coupled with an action that triggers a device
|
||||
* request from an api such as WebBluetooth.
|
||||
*
|
||||
* :::caution
|
||||
*
|
||||
* This must be called before the device request is made. It will not return a
|
||||
* currently active device prompt.
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const [devicePrompt] = Promise.all([
|
||||
* frame.waitForDevicePrompt(),
|
||||
* frame.click('#connect-bluetooth'),
|
||||
* ]);
|
||||
* await devicePrompt.select(
|
||||
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
waitForDevicePrompt(
|
||||
options?: WaitTimeoutOptions
|
||||
): Promise<DeviceRequestPrompt>;
|
||||
waitForDevicePrompt(): Promise<DeviceRequestPrompt> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {CDPSession} from '../common/Connection.js';
|
||||
import {Frame} from '../common/Frame.js';
|
||||
|
||||
import {Frame} from './Frame.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
|
||||
/**
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
import Protocol from 'devtools-protocol';
|
||||
|
||||
import {Frame} from '../common/Frame.js';
|
||||
import {SecurityDetails} from '../common/SecurityDetails.js';
|
||||
|
||||
import {Frame} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
|
||||
/**
|
||||
|
@ -28,12 +28,6 @@ import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||
import type {Dialog} from '../common/Dialog.js';
|
||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||
import type {FileChooser} from '../common/FileChooser.js';
|
||||
import type {
|
||||
Frame,
|
||||
FrameAddScriptTagOptions,
|
||||
FrameAddStyleTagOptions,
|
||||
FrameWaitForFunctionOptions,
|
||||
} from '../common/Frame.js';
|
||||
import type {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
|
||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
|
||||
@ -60,6 +54,12 @@ import {assert} from '../util/assert.js';
|
||||
import type {Browser} from './Browser.js';
|
||||
import type {BrowserContext} from './BrowserContext.js';
|
||||
import type {ClickOptions, ElementHandle} from './ElementHandle.js';
|
||||
import type {
|
||||
Frame,
|
||||
FrameAddScriptTagOptions,
|
||||
FrameAddStyleTagOptions,
|
||||
FrameWaitForFunctionOptions,
|
||||
} from './Frame.js';
|
||||
import type {JSHandle} from './JSHandle.js';
|
||||
import {Locator} from './Locator.js';
|
||||
|
||||
|
@ -19,6 +19,7 @@ export * from './BrowserContext.js';
|
||||
export * from './Page.js';
|
||||
export * from './JSHandle.js';
|
||||
export * from './ElementHandle.js';
|
||||
export * from './Frame.js';
|
||||
export * from './HTTPResponse.js';
|
||||
export * from './HTTPRequest.js';
|
||||
export * from './Locator.js';
|
||||
|
@ -253,7 +253,7 @@ export class CDPElementHandle<
|
||||
|
||||
override async contentFrame(): Promise<Frame | null> {
|
||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
objectId: this.id,
|
||||
});
|
||||
if (typeof nodeInfo.node.frameId !== 'string') {
|
||||
return null;
|
||||
@ -268,7 +268,7 @@ export class CDPElementHandle<
|
||||
|
||||
try {
|
||||
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
objectId: this.id,
|
||||
});
|
||||
} catch (error) {
|
||||
debugError(error);
|
||||
@ -333,7 +333,7 @@ export class CDPElementHandle<
|
||||
const [result, layoutMetrics] = await Promise.all([
|
||||
this.client
|
||||
.send('DOM.getContentQuads', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
objectId: this.id,
|
||||
})
|
||||
.catch(debugError),
|
||||
(this.#page as CDPPage)._client().send('Page.getLayoutMetrics'),
|
||||
@ -583,9 +583,8 @@ export class CDPElementHandle<
|
||||
return path.resolve(filePath);
|
||||
}
|
||||
});
|
||||
const {objectId} = this.remoteObject();
|
||||
const {node} = await this.client.send('DOM.describeNode', {
|
||||
objectId,
|
||||
objectId: this.id,
|
||||
});
|
||||
const {backendNodeId} = node;
|
||||
|
||||
@ -603,7 +602,7 @@ export class CDPElementHandle<
|
||||
});
|
||||
} else {
|
||||
await this.client.send('DOM.setFileInputFiles', {
|
||||
objectId,
|
||||
objectId: this.id,
|
||||
files,
|
||||
backendNodeId,
|
||||
});
|
||||
|
@ -17,6 +17,12 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {type ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
||||
import {
|
||||
Frame as BaseFrame,
|
||||
FrameAddScriptTagOptions,
|
||||
FrameAddStyleTagOptions,
|
||||
FrameWaitForFunctionOptions,
|
||||
} from '../api/Frame.js';
|
||||
import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
@ -42,185 +48,29 @@ import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js';
|
||||
import {importFSPromises, withSourcePuppeteerURLIfNone} from './util.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @internal
|
||||
*/
|
||||
export interface FrameWaitForFunctionOptions {
|
||||
/**
|
||||
* An interval at which the `pageFunction` is executed, defaults to `raf`. If
|
||||
* `polling` is a number, then it is treated as an interval in milliseconds at
|
||||
* which the function would be executed. If `polling` is a string, then it can
|
||||
* be one of the following values:
|
||||
*
|
||||
* - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame`
|
||||
* callback. This is the tightest polling mode which is suitable to observe
|
||||
* styling changes.
|
||||
*
|
||||
* - `mutation` - to execute `pageFunction` on every DOM mutation.
|
||||
*/
|
||||
polling?: 'raf' | 'mutation' | number;
|
||||
/**
|
||||
* Maximum time to wait in milliseconds. Defaults to `30000` (30 seconds).
|
||||
* Pass `0` to disable the timeout. Puppeteer's default timeout can be changed
|
||||
* using {@link Page.setDefaultTimeout}.
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* A signal object that allows you to cancel a waitForFunction call.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FrameAddScriptTagOptions {
|
||||
/**
|
||||
* URL of the script to be added.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* Path to a JavaScript file to be injected into the frame.
|
||||
*
|
||||
* @remarks
|
||||
* If `path` is a relative path, it is resolved relative to the current
|
||||
* working directory (`process.cwd()` in Node.js).
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* JavaScript to be injected into the frame.
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* Sets the `type` of the script. Use `module` in order to load an ES2015 module.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Sets the `id` of the script.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FrameAddStyleTagOptions {
|
||||
/**
|
||||
* the URL of the CSS file to be added.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* The path to a CSS file to be injected into the frame.
|
||||
* @remarks
|
||||
* If `path` is a relative path, it is resolved relative to the current
|
||||
* working directory (`process.cwd()` in Node.js).
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* Raw CSS content to be injected into the frame.
|
||||
*/
|
||||
content?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a DOM frame.
|
||||
*
|
||||
* To understand frames, you can think of frames as `<iframe>` elements. Just
|
||||
* like iframes, frames can be nested, and when JavaScript is executed in a
|
||||
* frame, the JavaScript does not effect frames inside the ambient frame the
|
||||
* JavaScript executes in.
|
||||
*
|
||||
* @example
|
||||
* At any point in time, {@link Page | pages} expose their current frame
|
||||
* tree via the {@link Page.mainFrame} and {@link Frame.childFrames} methods.
|
||||
*
|
||||
* @example
|
||||
* An example of dumping frame tree:
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* const browser = await puppeteer.launch();
|
||||
* const page = await browser.newPage();
|
||||
* await page.goto('https://www.google.com/chrome/browser/canary.html');
|
||||
* dumpFrameTree(page.mainFrame(), '');
|
||||
* await browser.close();
|
||||
*
|
||||
* function dumpFrameTree(frame, indent) {
|
||||
* console.log(indent + frame.url());
|
||||
* for (const child of frame.childFrames()) {
|
||||
* dumpFrameTree(child, indent + ' ');
|
||||
* }
|
||||
* }
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* An example of getting text from an iframe element:
|
||||
*
|
||||
* ```ts
|
||||
* const frame = page.frames().find(frame => frame.name() === 'myframe');
|
||||
* const text = await frame.$eval('.selector', element => element.textContent);
|
||||
* console.log(text);
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* Frame lifecycles are controlled by three events that are all dispatched on
|
||||
* the parent {@link Frame.page | page}:
|
||||
*
|
||||
* - {@link PageEmittedEvents.FrameAttached}
|
||||
* - {@link PageEmittedEvents.FrameNavigated}
|
||||
* - {@link PageEmittedEvents.FrameDetached}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class Frame {
|
||||
export class Frame extends BaseFrame {
|
||||
#url = '';
|
||||
#detached = false;
|
||||
#client!: CDPSession;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
worlds!: IsolatedWorldChart;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override worlds!: IsolatedWorldChart;
|
||||
_frameManager: FrameManager;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_id: string;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override _id: string;
|
||||
_loaderId = '';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_name?: string;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_hasStartedLoading = false;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override _name?: string;
|
||||
override _hasStartedLoading = false;
|
||||
_lifecycleEvents = new Set<string>();
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_parentId?: string;
|
||||
override _parentId?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
frameManager: FrameManager,
|
||||
frameId: string,
|
||||
parentFrameId: string | undefined,
|
||||
client: CDPSession
|
||||
) {
|
||||
super();
|
||||
this._frameManager = frameManager;
|
||||
this.#url = '';
|
||||
this._id = frameId;
|
||||
@ -232,9 +82,6 @@ export class Frame {
|
||||
this.updateClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
updateClient(client: CDPSession): void {
|
||||
this.#client = client;
|
||||
this.worlds = {
|
||||
@ -243,59 +90,15 @@ export class Frame {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The page associated with the frame.
|
||||
*/
|
||||
page(): Page {
|
||||
override page(): Page {
|
||||
return this._frameManager.page();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `true` if the frame is an out-of-process (OOP) frame. Otherwise,
|
||||
* `false`.
|
||||
*/
|
||||
isOOPFrame(): boolean {
|
||||
override isOOPFrame(): boolean {
|
||||
return this.#client !== this._frameManager.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates a frame to the given url.
|
||||
*
|
||||
* @remarks
|
||||
* Navigation to `about:blank` or navigation to the same URL with a different
|
||||
* hash will succeed and return `null`.
|
||||
*
|
||||
* :::warning
|
||||
*
|
||||
* Headless mode doesn't support navigation to a PDF document. See the {@link
|
||||
* https://bugs.chromium.org/p/chromium/issues/detail?id=761295 | upstream
|
||||
* issue}.
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* @param url - the URL to navigate the frame to. This should include the
|
||||
* scheme, e.g. `https://`.
|
||||
* @param options - navigation options. `waitUntil` is useful to define when
|
||||
* the navigation should be considered successful - see the docs for
|
||||
* {@link PuppeteerLifeCycleEvent} for more details.
|
||||
*
|
||||
* @returns A promise which resolves to the main resource response. In case of
|
||||
* multiple redirects, the navigation will resolve with the response of the
|
||||
* last redirect.
|
||||
* @throws This method will throw an error if:
|
||||
*
|
||||
* - there's an SSL error (e.g. in case of self-signed certificates).
|
||||
* - target URL is invalid.
|
||||
* - the `timeout` is exceeded during navigation.
|
||||
* - the remote server does not respond or is unreachable.
|
||||
* - the main resource failed to load.
|
||||
*
|
||||
* This method will not throw an error when any valid HTTP status code is
|
||||
* returned by the remote server, including 404 "Not Found" and 500 "Internal
|
||||
* Server Error". The status code for such responses can be retrieved by
|
||||
* calling {@link HTTPResponse.status}.
|
||||
*/
|
||||
async goto(
|
||||
override async goto(
|
||||
url: string,
|
||||
options: {
|
||||
referer?: string;
|
||||
@ -378,30 +181,7 @@ export class Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the frame to navigate. It is useful for when you run code which
|
||||
* will indirectly cause the frame to navigate.
|
||||
*
|
||||
* Usage of the
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/History_API | History API}
|
||||
* to change the URL is considered a navigation.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const [response] = await Promise.all([
|
||||
* // The navigation promise resolves after navigation has finished
|
||||
* frame.waitForNavigation(),
|
||||
* // Clicking the link will indirectly cause a navigation
|
||||
* frame.click('a.my-link'),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param options - options to configure when the navigation is consided
|
||||
* finished.
|
||||
* @returns a promise that resolves when the frame navigates to a new URL.
|
||||
*/
|
||||
async waitForNavigation(
|
||||
override async waitForNavigation(
|
||||
options: {
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
@ -432,27 +212,15 @@ export class Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_client(): CDPSession {
|
||||
override _client(): CDPSession {
|
||||
return this.#client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
executionContext(): Promise<ExecutionContext> {
|
||||
override executionContext(): Promise<ExecutionContext> {
|
||||
return this.worlds[MAIN_WORLD].executionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to {@link Page.evaluateHandle} except it's run within
|
||||
* the context of this frame.
|
||||
*
|
||||
* @see {@link Page.evaluateHandle} for details.
|
||||
*/
|
||||
async evaluateHandle<
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
@ -466,13 +234,7 @@ export class Frame {
|
||||
return this.worlds[MAIN_WORLD].evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to {@link Page.evaluate} except it's run within the
|
||||
* the context of this frame.
|
||||
*
|
||||
* @see {@link Page.evaluate} for details.
|
||||
*/
|
||||
async evaluate<
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
@ -486,53 +248,19 @@ export class Frame {
|
||||
return this.worlds[MAIN_WORLD].evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the frame for an element matching the given selector.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @returns A {@link ElementHandle | element handle} to the first element
|
||||
* matching the given selector. Otherwise, `null`.
|
||||
*/
|
||||
async $<Selector extends string>(
|
||||
override async $<Selector extends string>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
return this.worlds[MAIN_WORLD].$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the frame for all elements matching the given selector.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @returns An array of {@link ElementHandle | element handles} that point to
|
||||
* elements matching the given selector.
|
||||
*/
|
||||
async $$<Selector extends string>(
|
||||
override async $$<Selector extends string>(
|
||||
selector: Selector
|
||||
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
|
||||
return this.worlds[MAIN_WORLD].$$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given function on the first element matching the given selector in
|
||||
* the frame.
|
||||
*
|
||||
* If the given function returns a promise, then this method will wait till
|
||||
* the promise resolves.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const searchValue = await frame.$eval('#search', el => el.value);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param pageFunction - The function to be evaluated in the frame's context.
|
||||
* The first element matching the selector will be passed to the function as
|
||||
* its first argument.
|
||||
* @param args - Additional arguments to pass to `pageFunction`.
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
async $eval<
|
||||
override async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
@ -548,27 +276,7 @@ export class Frame {
|
||||
return this.worlds[MAIN_WORLD].$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given function on an array of elements matching the given selector
|
||||
* in the frame.
|
||||
*
|
||||
* If the given function returns a promise, then this method will wait till
|
||||
* the promise resolves.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const divsCounts = await frame.$$eval('div', divs => divs.length);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param pageFunction - The function to be evaluated in the frame's context.
|
||||
* An array of elements matching the given selector will be passed to the
|
||||
* function as its first argument.
|
||||
* @param args - Additional arguments to pass to `pageFunction`.
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
async $$eval<
|
||||
override async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<
|
||||
@ -584,56 +292,11 @@ export class Frame {
|
||||
return this.worlds[MAIN_WORLD].$$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Frame.$$} with the `xpath` prefix.
|
||||
*
|
||||
* Example: `await frame.$$('xpath/' + xpathExpression)`
|
||||
*
|
||||
* This method evaluates the given XPath expression and returns the results.
|
||||
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
|
||||
* automatically.
|
||||
* @param expression - the XPath expression to evaluate.
|
||||
*/
|
||||
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||
override async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||
return this.worlds[MAIN_WORLD].$x(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an element matching the given selector to appear in the frame.
|
||||
*
|
||||
* This method works across navigations.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* const browser = await puppeteer.launch();
|
||||
* const page = await browser.newPage();
|
||||
* let currentURL;
|
||||
* page
|
||||
* .mainFrame()
|
||||
* .waitForSelector('img')
|
||||
* .then(() => console.log('First URL with image: ' + currentURL));
|
||||
*
|
||||
* for (currentURL of [
|
||||
* 'https://example.com',
|
||||
* 'https://google.com',
|
||||
* 'https://bbc.com',
|
||||
* ]) {
|
||||
* await page.goto(currentURL);
|
||||
* }
|
||||
* await browser.close();
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query and wait for.
|
||||
* @param options - Options for customizing waiting behavior.
|
||||
* @returns An element matching the given selector.
|
||||
* @throws Throws if an element matching the given selector doesn't appear.
|
||||
*/
|
||||
async waitForSelector<Selector extends string>(
|
||||
override async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
@ -646,29 +309,7 @@ export class Frame {
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Frame.waitForSelector} with the `xpath` prefix.
|
||||
*
|
||||
* Example: `await frame.waitForSelector('xpath/' + xpathExpression)`
|
||||
*
|
||||
* The method evaluates the XPath expression relative to the Frame.
|
||||
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
|
||||
* automatically.
|
||||
*
|
||||
* Wait for the `xpath` to appear in page. If at the moment of calling the
|
||||
* method the `xpath` already exists, the method will return immediately. If
|
||||
* the xpath doesn't appear after the `timeout` milliseconds of waiting, the
|
||||
* function will throw.
|
||||
*
|
||||
* For a code example, see the example for {@link Frame.waitForSelector}. That
|
||||
* function behaves identically other than taking a CSS selector rather than
|
||||
* an XPath.
|
||||
*
|
||||
* @param xpath - the XPath expression to wait for.
|
||||
* @param options - options to configure the visibility of the element and how
|
||||
* long to wait before timing out.
|
||||
*/
|
||||
async waitForXPath(
|
||||
override async waitForXPath(
|
||||
xpath: string,
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<Node> | null> {
|
||||
@ -678,40 +319,7 @@ export class Frame {
|
||||
return this.waitForSelector(`xpath/${xpath}`, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* The `waitForFunction` can be used to observe viewport size change:
|
||||
*
|
||||
* ```ts
|
||||
* import puppeteer from 'puppeteer';
|
||||
*
|
||||
* (async () => {
|
||||
* . const browser = await puppeteer.launch();
|
||||
* . const page = await browser.newPage();
|
||||
* . const watchDog = page.mainFrame().waitForFunction('window.innerWidth < 100');
|
||||
* . page.setViewport({width: 50, height: 50});
|
||||
* . await watchDog;
|
||||
* . await browser.close();
|
||||
* })();
|
||||
* ```
|
||||
*
|
||||
* To pass arguments from Node.js to the predicate of `page.waitForFunction` function:
|
||||
*
|
||||
* ```ts
|
||||
* const selector = '.foo';
|
||||
* await frame.waitForFunction(
|
||||
* selector => !!document.querySelector(selector),
|
||||
* {}, // empty options object
|
||||
* selector
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction - the function to evaluate in the frame context.
|
||||
* @param options - options to configure the polling method and timeout.
|
||||
* @param args - arguments to pass to the `pageFunction`.
|
||||
* @returns the promise which resolve when the `pageFunction` returns a truthy value.
|
||||
*/
|
||||
waitForFunction<
|
||||
override waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
@ -726,21 +334,11 @@ export class Frame {
|
||||
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full HTML contents of the frame, including the DOCTYPE.
|
||||
*/
|
||||
async content(): Promise<string> {
|
||||
override async content(): Promise<string> {
|
||||
return this.worlds[PUPPETEER_WORLD].content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of the frame.
|
||||
*
|
||||
* @param html - HTML markup to assign to the page.
|
||||
* @param options - Options to configure how long before timing out and at
|
||||
* what point to consider the content setting successful.
|
||||
*/
|
||||
async setContent(
|
||||
override async setContent(
|
||||
html: string,
|
||||
options: {
|
||||
timeout?: number;
|
||||
@ -750,56 +348,27 @@ export class Frame {
|
||||
return this.worlds[PUPPETEER_WORLD].setContent(html, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's `name` attribute as specified in the tag.
|
||||
*
|
||||
* @remarks
|
||||
* If the name is empty, it returns the `id` attribute instead.
|
||||
*
|
||||
* @remarks
|
||||
* This value is calculated once when the frame is created, and will not
|
||||
* update if the attribute is changed later.
|
||||
*/
|
||||
name(): string {
|
||||
override name(): string {
|
||||
return this._name || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's URL.
|
||||
*/
|
||||
url(): string {
|
||||
override url(): string {
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent frame, if any. Detached and main frames return `null`.
|
||||
*/
|
||||
parentFrame(): Frame | null {
|
||||
override parentFrame(): Frame | null {
|
||||
return this._frameManager._frameTree.parentFrame(this._id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of child frames.
|
||||
*/
|
||||
childFrames(): Frame[] {
|
||||
override childFrames(): Frame[] {
|
||||
return this._frameManager._frameTree.childFrames(this._id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is`true` if the frame has been detached. Otherwise, `false`.
|
||||
*/
|
||||
isDetached(): boolean {
|
||||
override isDetached(): boolean {
|
||||
return this.#detached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a `<script>` tag into the page with the desired url or content.
|
||||
*
|
||||
* @param options - Options for the script.
|
||||
* @returns An {@link ElementHandle | element handle} to the injected
|
||||
* `<script>` element.
|
||||
*/
|
||||
async addScriptTag(
|
||||
override async addScriptTag(
|
||||
options: FrameAddScriptTagOptions
|
||||
): Promise<ElementHandle<HTMLScriptElement>> {
|
||||
let {content = '', type} = options;
|
||||
@ -861,20 +430,13 @@ export class Frame {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a `<link rel="stylesheet">` tag into the page with the desired URL or
|
||||
* a `<style type="text/css">` tag with the content.
|
||||
*
|
||||
* @returns An {@link ElementHandle | element handle} to the loaded `<link>`
|
||||
* or `<style>` element.
|
||||
*/
|
||||
async addStyleTag(
|
||||
override async addStyleTag(
|
||||
options: Omit<FrameAddStyleTagOptions, 'url'>
|
||||
): Promise<ElementHandle<HTMLStyleElement>>;
|
||||
async addStyleTag(
|
||||
override async addStyleTag(
|
||||
options: FrameAddStyleTagOptions
|
||||
): Promise<ElementHandle<HTMLLinkElement>>;
|
||||
async addStyleTag(
|
||||
override async addStyleTag(
|
||||
options: FrameAddStyleTagOptions
|
||||
): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> {
|
||||
let {content = ''} = options;
|
||||
@ -937,106 +499,30 @@ export class Frame {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the first element found that matches `selector`.
|
||||
*
|
||||
* @remarks
|
||||
* If `click()` triggers a navigation event and there's a separate
|
||||
* `page.waitForNavigation()` promise to be resolved, you may end up with a
|
||||
* race condition that yields unexpected results. The correct pattern for
|
||||
* click and wait for navigation is the following:
|
||||
*
|
||||
* ```ts
|
||||
* const [response] = await Promise.all([
|
||||
* page.waitForNavigation(waitOptions),
|
||||
* frame.click(selector, clickOptions),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
*/
|
||||
async click(
|
||||
override async click(
|
||||
selector: string,
|
||||
options: Readonly<ClickOptions> = {}
|
||||
): Promise<void> {
|
||||
return this.worlds[PUPPETEER_WORLD].click(selector, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the first element that matches the `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async focus(selector: string): Promise<void> {
|
||||
override async focus(selector: string): Promise<void> {
|
||||
return this.worlds[PUPPETEER_WORLD].focus(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovers the pointer over the center of the first element that matches the
|
||||
* `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async hover(selector: string): Promise<void> {
|
||||
override async hover(selector: string): Promise<void> {
|
||||
return this.worlds[PUPPETEER_WORLD].hover(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a set of value on the first `<select>` element that matches the
|
||||
* `selector`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* frame.select('select#colors', 'blue'); // single selection
|
||||
* frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections
|
||||
* ```
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @param values - The array of values to select. If the `<select>` has the
|
||||
* `multiple` attribute, all values are considered, otherwise only the first
|
||||
* one is taken into account.
|
||||
* @returns the list of values that were successfully selected.
|
||||
* @throws Throws if there's no `<select>` matching `selector`.
|
||||
*/
|
||||
select(selector: string, ...values: string[]): Promise<string[]> {
|
||||
override select(selector: string, ...values: string[]): Promise<string[]> {
|
||||
return this.worlds[PUPPETEER_WORLD].select(selector, ...values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Taps the first element that matches the `selector`.
|
||||
*
|
||||
* @param selector - The selector to query for.
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
async tap(selector: string): Promise<void> {
|
||||
override async tap(selector: string): Promise<void> {
|
||||
return this.worlds[PUPPETEER_WORLD].tap(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character
|
||||
* in the text.
|
||||
*
|
||||
* @remarks
|
||||
* To press a special key, like `Control` or `ArrowDown`, use
|
||||
* {@link Keyboard.press}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* await frame.type('#mytextarea', 'Hello'); // Types instantly
|
||||
* await frame.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user
|
||||
* ```
|
||||
*
|
||||
* @param selector - the selector for the element to type into. If there are
|
||||
* multiple the first will be used.
|
||||
* @param text - text to type into the element
|
||||
* @param options - takes one option, `delay`, which sets the time to wait
|
||||
* between key presses in milliseconds. Defaults to `0`.
|
||||
*/
|
||||
async type(
|
||||
override async type(
|
||||
selector: string,
|
||||
text: string,
|
||||
options?: {delay: number}
|
||||
@ -1044,42 +530,16 @@ export class Frame {
|
||||
return this.worlds[PUPPETEER_WORLD].type(selector, text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replace with `new Promise(r => setTimeout(r, milliseconds));`.
|
||||
*
|
||||
* Causes your script to wait for the given number of milliseconds.
|
||||
*
|
||||
* @remarks
|
||||
* It's generally recommended to not wait for a number of seconds, but instead
|
||||
* use {@link Frame.waitForSelector}, {@link Frame.waitForXPath} or
|
||||
* {@link Frame.waitForFunction} to wait for exactly the conditions you want.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Wait for 1 second:
|
||||
*
|
||||
* ```ts
|
||||
* await frame.waitForTimeout(1000);
|
||||
* ```
|
||||
*
|
||||
* @param milliseconds - the number of milliseconds to wait.
|
||||
*/
|
||||
waitForTimeout(milliseconds: number): Promise<void> {
|
||||
override waitForTimeout(milliseconds: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame's title.
|
||||
*/
|
||||
async title(): Promise<string> {
|
||||
override async title(): Promise<string> {
|
||||
return this.worlds[PUPPETEER_WORLD].title();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_deviceRequestPromptManager(): DeviceRequestPromptManager {
|
||||
if (this.isOOPFrame()) {
|
||||
return this._frameManager._deviceRequestPromptManager(this.#client);
|
||||
@ -1089,53 +549,21 @@ export class Frame {
|
||||
return parentFrame._deviceRequestPromptManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is typically coupled with an action that triggers a device
|
||||
* request from an api such as WebBluetooth.
|
||||
*
|
||||
* :::caution
|
||||
*
|
||||
* This must be called before the device request is made. It will not return a
|
||||
* currently active device prompt.
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const [devicePrompt] = Promise.all([
|
||||
* frame.waitForDevicePrompt(),
|
||||
* frame.click('#connect-bluetooth'),
|
||||
* ]);
|
||||
* await devicePrompt.select(
|
||||
* await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
waitForDevicePrompt(
|
||||
override waitForDevicePrompt(
|
||||
options: WaitTimeoutOptions = {}
|
||||
): Promise<DeviceRequestPrompt> {
|
||||
return this._deviceRequestPromptManager().waitForDevicePrompt(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_navigated(framePayload: Protocol.Page.Frame): void {
|
||||
this._name = framePayload.name;
|
||||
this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_navigatedWithinDocument(url: string): void {
|
||||
this.#url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_onLifecycleEvent(loaderId: string, name: string): void {
|
||||
if (name === 'init') {
|
||||
this._loaderId = loaderId;
|
||||
@ -1144,24 +572,15 @@ export class Frame {
|
||||
this._lifecycleEvents.add(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_onLoadingStopped(): void {
|
||||
this._lifecycleEvents.add('DOMContentLoaded');
|
||||
this._lifecycleEvents.add('load');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_onLoadingStarted(): void {
|
||||
this._hasStartedLoading = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_detach(): void {
|
||||
this.#detached = true;
|
||||
this.worlds[MAIN_WORLD]._detach();
|
||||
|
@ -25,6 +25,7 @@ import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {Frame as CDPFrame} from './Frame.js';
|
||||
import {FrameTree} from './FrameTree.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
@ -69,7 +70,7 @@ export class FrameManager extends EventEmitter {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_frameTree = new FrameTree();
|
||||
_frameTree = new FrameTree<Frame>();
|
||||
|
||||
/**
|
||||
* Set of frame IDs stored to indicate if a frame has received a
|
||||
@ -305,7 +306,7 @@ export class FrameManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
frame = new Frame(this, frameId, parentFrameId, session);
|
||||
frame = new CDPFrame(this, frameId, parentFrameId, session);
|
||||
this._frameTree.addFrame(frame);
|
||||
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
||||
}
|
||||
@ -331,7 +332,7 @@ export class FrameManager extends EventEmitter {
|
||||
frame._id = frameId;
|
||||
} else {
|
||||
// Initial main frame navigation.
|
||||
frame = new Frame(this, frameId, undefined, this.#client);
|
||||
frame = new CDPFrame(this, frameId, undefined, this.#client);
|
||||
}
|
||||
this._frameTree.addFrame(frame);
|
||||
}
|
||||
|
@ -14,13 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Frame as BaseFrame} from '../api/Frame.js';
|
||||
import {
|
||||
createDeferredPromise,
|
||||
DeferredPromise,
|
||||
} from '../util/DeferredPromise.js';
|
||||
|
||||
import type {Frame} from './Frame.js';
|
||||
|
||||
/**
|
||||
* Keeps track of the page frame tree and it's is managed by
|
||||
* {@link FrameManager}. FrameTree uses frame IDs to reference frame and it
|
||||
@ -28,7 +27,7 @@ import type {Frame} from './Frame.js';
|
||||
* structure is eventually consistent.
|
||||
* @internal
|
||||
*/
|
||||
export class FrameTree {
|
||||
export class FrameTree<Frame extends BaseFrame> {
|
||||
#frames = new Map<string, Frame>();
|
||||
// frameID -> parentFrameID
|
||||
#parentIds = new Map<string, string>();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Frame} from '../api/Frame.js';
|
||||
import {
|
||||
ContinueRequestOverrides,
|
||||
ErrorCode,
|
||||
@ -31,7 +32,6 @@ import {assert} from '../util/assert.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ProtocolError} from './Errors.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {debugError, isString} from './util.js';
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Frame} from '../api/Frame.js';
|
||||
import {
|
||||
HTTPResponse as BaseHTTPResponse,
|
||||
RemoteAddress,
|
||||
@ -23,7 +24,6 @@ import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ProtocolError} from './Errors.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {SecurityDetails} from './SecurityDetails.js';
|
||||
|
||||
|
@ -21,6 +21,12 @@ import {Protocol} from 'devtools-protocol';
|
||||
import type {Browser} from '../api/Browser.js';
|
||||
import type {BrowserContext} from '../api/BrowserContext.js';
|
||||
import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
||||
import {
|
||||
Frame,
|
||||
FrameAddScriptTagOptions,
|
||||
FrameAddStyleTagOptions,
|
||||
FrameWaitForFunctionOptions,
|
||||
} from '../api/Frame.js';
|
||||
import {HTTPRequest} from '../api/HTTPRequest.js';
|
||||
import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
@ -55,12 +61,6 @@ import {DeviceRequestPrompt} from './DeviceRequestPrompt.js';
|
||||
import {Dialog} from './Dialog.js';
|
||||
import {EmulationManager} from './EmulationManager.js';
|
||||
import {FileChooser} from './FileChooser.js';
|
||||
import {
|
||||
Frame,
|
||||
FrameAddScriptTagOptions,
|
||||
FrameAddStyleTagOptions,
|
||||
FrameWaitForFunctionOptions,
|
||||
} from './Frame.js';
|
||||
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||
import {Keyboard, Mouse, Touchscreen} from './Input.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
@ -895,21 +895,21 @@ export class CDPPage extends Page {
|
||||
}
|
||||
|
||||
override async content(): Promise<string> {
|
||||
return await this.#frameManager.mainFrame().content();
|
||||
return await this.mainFrame().content();
|
||||
}
|
||||
|
||||
override async setContent(
|
||||
html: string,
|
||||
options: WaitForOptions = {}
|
||||
): Promise<void> {
|
||||
await this.#frameManager.mainFrame().setContent(html, options);
|
||||
await this.mainFrame().setContent(html, options);
|
||||
}
|
||||
|
||||
override async goto(
|
||||
url: string,
|
||||
options: WaitForOptions & {referer?: string; referrerPolicy?: string} = {}
|
||||
): Promise<HTTPResponse | null> {
|
||||
return await this.#frameManager.mainFrame().goto(url, options);
|
||||
return await this.mainFrame().goto(url, options);
|
||||
}
|
||||
|
||||
override async reload(
|
||||
@ -926,7 +926,7 @@ export class CDPPage extends Page {
|
||||
override async waitForNavigation(
|
||||
options: WaitForOptions = {}
|
||||
): Promise<HTTPResponse | null> {
|
||||
return await this.#frameManager.mainFrame().waitForNavigation(options);
|
||||
return await this.mainFrame().waitForNavigation(options);
|
||||
}
|
||||
|
||||
#sessionClosePromise(): Promise<Error> {
|
||||
@ -1163,7 +1163,7 @@ export class CDPPage extends Page {
|
||||
'Throttling rate should be greater or equal to 1'
|
||||
);
|
||||
await this.#client.send('Emulation.setCPUThrottlingRate', {
|
||||
rate: factor !== null ? factor : 1,
|
||||
rate: factor ?? 1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1265,7 +1265,7 @@ export class CDPPage extends Page {
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return this.#frameManager.mainFrame().evaluate(pageFunction, ...args);
|
||||
return this.mainFrame().evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async evaluateOnNewDocument<
|
||||
|
@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
import type {Frame} from './Frame.js';
|
||||
import {transposeIterableHandle} from './HandleIterator.js';
|
||||
import type {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
|
@ -33,16 +33,18 @@ import {Connection} from './Connection.js';
|
||||
* @internal
|
||||
*/
|
||||
export class Browser extends BrowserBase {
|
||||
static readonly subscribeModules = ['browsingContext'];
|
||||
|
||||
static async create(opts: Options): Promise<Browser> {
|
||||
// TODO: await until the connection is established.
|
||||
try {
|
||||
await opts.connection.send('session.new', {});
|
||||
} catch {}
|
||||
|
||||
await opts.connection.send('session.subscribe', {
|
||||
events: [
|
||||
'browsingContext.contextCreated',
|
||||
] as Bidi.Session.SubscribeParametersEvent[],
|
||||
events: Browser.subscribeModules as Bidi.Message.EventNames[],
|
||||
});
|
||||
|
||||
return new Browser(opts);
|
||||
}
|
||||
|
||||
|
@ -14,13 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||
import {Page as PageBase} from '../../api/Page.js';
|
||||
import {Viewport} from '../PuppeteerViewport.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {Context} from './Context.js';
|
||||
import {Page} from './Page.js';
|
||||
import {debugError} from './utils.js';
|
||||
|
||||
interface BrowserContextOptions {
|
||||
defaultViewport: Viewport | null;
|
||||
@ -32,19 +34,34 @@ interface BrowserContextOptions {
|
||||
export class BrowserContext extends BrowserContextBase {
|
||||
#connection: Connection;
|
||||
#defaultViewport: Viewport | null;
|
||||
#pages = new Map<string, Page>();
|
||||
#onContextDestroyedBind = this.#onContextDestroyed.bind(this);
|
||||
|
||||
constructor(connection: Connection, options: BrowserContextOptions) {
|
||||
super();
|
||||
this.#connection = connection;
|
||||
this.#defaultViewport = options.defaultViewport;
|
||||
this.#connection.on(
|
||||
'browsingContext.contextDestroyed',
|
||||
this.#onContextDestroyedBind
|
||||
);
|
||||
}
|
||||
|
||||
async #onContextDestroyed(
|
||||
event: Bidi.BrowsingContext.ContextDestroyedEvent['params']
|
||||
) {
|
||||
const page = this.#pages.get(event.context);
|
||||
await page?.close().catch(error => {
|
||||
debugError(error);
|
||||
});
|
||||
this.#pages.delete(event.context);
|
||||
}
|
||||
|
||||
override async newPage(): Promise<PageBase> {
|
||||
const {result} = await this.#connection.send('browsingContext.create', {
|
||||
type: 'tab',
|
||||
});
|
||||
const context = this.#connection.context(result.context) as Context;
|
||||
const page = new Page(context);
|
||||
const page = await Page._create(this.#connection, result);
|
||||
if (this.#defaultViewport) {
|
||||
try {
|
||||
await page.setViewport(this.#defaultViewport);
|
||||
@ -52,8 +69,18 @@ export class BrowserContext extends BrowserContextBase {
|
||||
// No support for setViewport in Firefox.
|
||||
}
|
||||
}
|
||||
|
||||
this.#pages.set(result.context, page);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
override async close(): Promise<void> {}
|
||||
override async close(): Promise<void> {
|
||||
for (const page of this.#pages.values()) {
|
||||
await page?.close().catch(error => {
|
||||
debugError(error);
|
||||
});
|
||||
}
|
||||
this.#pages = new Map();
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,11 @@ interface Commands {
|
||||
};
|
||||
'browsingContext.close': {
|
||||
params: Bidi.BrowsingContext.CloseParameters;
|
||||
returnType: Bidi.BrowsingContext.CloseResult;
|
||||
returnType: Bidi.Message.EmptyResult;
|
||||
};
|
||||
'browsingContext.getTree': {
|
||||
params: Bidi.BrowsingContext.GetTreeParameters;
|
||||
returnType: Bidi.BrowsingContext.GetTreeResult;
|
||||
};
|
||||
'browsingContext.navigate': {
|
||||
params: Bidi.BrowsingContext.NavigateParameters;
|
||||
@ -73,11 +77,11 @@ interface Commands {
|
||||
returnType: Bidi.Session.StatusResult;
|
||||
};
|
||||
'session.subscribe': {
|
||||
params: Bidi.Session.SubscribeParameters;
|
||||
params: Bidi.Session.SubscriptionRequest;
|
||||
returnType: Bidi.Session.SubscribeResult;
|
||||
};
|
||||
'session.unsubscribe': {
|
||||
params: Bidi.Session.SubscribeParameters;
|
||||
params: Bidi.Session.SubscriptionRequest;
|
||||
returnType: Bidi.Session.UnsubscribeResult;
|
||||
};
|
||||
'cdp.sendCommand': {
|
||||
@ -159,7 +163,6 @@ export class Connection extends EventEmitter {
|
||||
this.#callbacks.resolve(object.id, object);
|
||||
}
|
||||
} else {
|
||||
this.#handleSpecialEvents(object);
|
||||
this.#maybeEmitOnContext(object);
|
||||
this.emit(object.method, object.params);
|
||||
}
|
||||
@ -177,14 +180,12 @@ export class Connection extends EventEmitter {
|
||||
context?.emit(event.method, event.params);
|
||||
}
|
||||
|
||||
#handleSpecialEvents(event: Bidi.Message.EventMessage) {
|
||||
switch (event.method) {
|
||||
case 'browsingContext.contextCreated':
|
||||
this.#contexts.set(
|
||||
event.params.context,
|
||||
new Context(this, event.params)
|
||||
);
|
||||
}
|
||||
registerContext(context: Context): void {
|
||||
this.#contexts.set(context.id, context);
|
||||
}
|
||||
|
||||
unregisterContext(context: Context): void {
|
||||
this.#contexts.delete(context.id);
|
||||
}
|
||||
|
||||
#onClose(): void {
|
||||
|
@ -24,7 +24,6 @@ import {ProtocolMapping} from '../Connection.js';
|
||||
import {ProtocolError, TimeoutError} from '../Errors.js';
|
||||
import {EventEmitter} from '../EventEmitter.js';
|
||||
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
import {
|
||||
getSourcePuppeteerURLIfAvailable,
|
||||
@ -36,6 +35,7 @@ import {
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
import {createEvaluationError} from './utils.js';
|
||||
@ -71,14 +71,28 @@ const lifeCycleToSubscribedEvent = new Map<PuppeteerLifeCycleEvent, string>([
|
||||
export class Context extends EventEmitter {
|
||||
#connection: Connection;
|
||||
#url: string;
|
||||
_contextId: string;
|
||||
_timeoutSettings = new TimeoutSettings();
|
||||
#id: string;
|
||||
#parentId?: string | null;
|
||||
_frameManager: FrameManager;
|
||||
|
||||
constructor(connection: Connection, result: Bidi.BrowsingContext.Info) {
|
||||
constructor(
|
||||
connection: Connection,
|
||||
frameManager: FrameManager,
|
||||
result: Bidi.BrowsingContext.Info
|
||||
) {
|
||||
super();
|
||||
this.#connection = connection;
|
||||
this._contextId = result.context;
|
||||
this._frameManager = frameManager;
|
||||
this.#id = result.context;
|
||||
this.#parentId = result.parent;
|
||||
this.#url = result.url;
|
||||
|
||||
this.on(
|
||||
'browsingContext.fragmentNavigated',
|
||||
(info: Bidi.BrowsingContext.NavigationInfo) => {
|
||||
this.#url = info.url;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
get connection(): Connection {
|
||||
@ -86,7 +100,11 @@ export class Context extends EventEmitter {
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._contextId;
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
get parentId(): string | undefined | null {
|
||||
return this.#parentId;
|
||||
}
|
||||
|
||||
async evaluateHandle<
|
||||
@ -146,8 +164,8 @@ export class Context extends EventEmitter {
|
||||
: `${pageFunction}\n${sourceUrlComment}\n`;
|
||||
|
||||
responsePromise = this.#connection.send('script.evaluate', {
|
||||
expression: expression,
|
||||
target: {context: this._contextId},
|
||||
expression,
|
||||
target: {context: this.#id},
|
||||
resultOwnership,
|
||||
awaitPromise: true,
|
||||
});
|
||||
@ -163,7 +181,7 @@ export class Context extends EventEmitter {
|
||||
return BidiSerializer.serialize(arg, this);
|
||||
})
|
||||
),
|
||||
target: {context: this._contextId},
|
||||
target: {context: this.#id},
|
||||
resultOwnership,
|
||||
awaitPromise: true,
|
||||
});
|
||||
@ -189,7 +207,7 @@ export class Context extends EventEmitter {
|
||||
): Promise<HTTPResponse | null> {
|
||||
const {
|
||||
waitUntil = 'load',
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
timeout = this._frameManager._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const readinessState = lifeCycleToReadinessState.get(
|
||||
@ -229,7 +247,7 @@ export class Context extends EventEmitter {
|
||||
): Promise<void> {
|
||||
const {
|
||||
waitUntil = 'load',
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
timeout = this._frameManager._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const waitUntilCommand = lifeCycleToSubscribedEvent.get(
|
||||
@ -250,12 +268,25 @@ export class Context extends EventEmitter {
|
||||
]);
|
||||
}
|
||||
|
||||
async content(): Promise<string> {
|
||||
return await this.evaluate(() => {
|
||||
let retVal = '';
|
||||
if (document.doctype) {
|
||||
retVal = new XMLSerializer().serializeToString(document.doctype);
|
||||
}
|
||||
if (document.documentElement) {
|
||||
retVal += document.documentElement.outerHTML;
|
||||
}
|
||||
return retVal;
|
||||
});
|
||||
}
|
||||
|
||||
async sendCDPCommand(
|
||||
method: keyof ProtocolMapping.Commands,
|
||||
params: object = {}
|
||||
): Promise<unknown> {
|
||||
const session = await this.#connection.send('cdp.getSession', {
|
||||
context: this._contextId,
|
||||
context: this.id,
|
||||
});
|
||||
// TODO: remove any once chromium-bidi types are updated.
|
||||
const sessionId = (session.result as any).cdpSession;
|
||||
|
125
packages/puppeteer-core/src/common/bidi/Frame.ts
Normal file
125
packages/puppeteer-core/src/common/bidi/Frame.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 {Frame as BaseFrame} from '../../api/Frame.js';
|
||||
import {HTTPResponse} from '../../api/HTTPResponse.js';
|
||||
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
|
||||
import {Context} from './Context.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {Page} from './Page.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Frame extends BaseFrame {
|
||||
_frameManager: FrameManager;
|
||||
_context: Context;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(frameManager: FrameManager, context: Context) {
|
||||
super();
|
||||
this._frameManager = frameManager;
|
||||
this._context = context;
|
||||
this._id = context.id;
|
||||
this._parentId = context.parentId ?? undefined;
|
||||
}
|
||||
|
||||
override page(): Page {
|
||||
return this._frameManager.page();
|
||||
}
|
||||
|
||||
override async goto(
|
||||
url: string,
|
||||
options?: {
|
||||
referer?: string;
|
||||
referrerPolicy?: string;
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}
|
||||
): Promise<HTTPResponse | null> {
|
||||
return this._context.goto(url, options);
|
||||
}
|
||||
|
||||
override async waitForNavigation(options?: {
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}): Promise<HTTPResponse | null>;
|
||||
override async waitForNavigation(): Promise<HTTPResponse | null> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this._context.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this._context.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async content(): Promise<string> {
|
||||
return this._context.content();
|
||||
}
|
||||
|
||||
override async setContent(
|
||||
html: string,
|
||||
options: {
|
||||
timeout?: number;
|
||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||
}
|
||||
): Promise<void> {
|
||||
return this._context.setContent(html, options);
|
||||
}
|
||||
|
||||
override name(): string {
|
||||
return this._name || '';
|
||||
}
|
||||
|
||||
override url(): string {
|
||||
return this._context.url();
|
||||
}
|
||||
|
||||
override parentFrame(): Frame | null {
|
||||
return this._frameManager._frameTree.parentFrame(this._id) ?? null;
|
||||
}
|
||||
|
||||
override childFrames(): Frame[] {
|
||||
return this._frameManager._frameTree.childFrames(this._id);
|
||||
}
|
||||
|
||||
override isDetached(): boolean {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
override async title(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
156
packages/puppeteer-core/src/common/bidi/FrameManager.ts
Normal file
156
packages/puppeteer-core/src/common/bidi/FrameManager.ts
Normal file
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright 2017 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 * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
import {assert} from '../../util/assert.js';
|
||||
import {EventEmitter, Handler} from '../EventEmitter.js';
|
||||
import {FrameManagerEmittedEvents} from '../FrameManager.js';
|
||||
import {FrameTree} from '../FrameTree.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {Context} from './Context.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {Page} from './Page.js';
|
||||
|
||||
/**
|
||||
* A frame manager manages the frames for a given {@link Page | page}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class FrameManager extends EventEmitter {
|
||||
#page: Page;
|
||||
#connection: Connection;
|
||||
_contextId: string;
|
||||
_frameTree = new FrameTree<Frame>();
|
||||
_timeoutSettings: TimeoutSettings;
|
||||
|
||||
get client(): Connection {
|
||||
return this.#connection;
|
||||
}
|
||||
|
||||
// TODO: switch string to (typeof Browser.events)[number]
|
||||
#subscribedEvents = new Map<string, Handler<any>>([
|
||||
['browsingContext.contextCreated', this.#onFrameAttached.bind(this)],
|
||||
['browsingContext.contextDestroyed', this.#onFrameDetached.bind(this)],
|
||||
['browsingContext.fragmentNavigated', this.#onFrameNavigated.bind(this)],
|
||||
]);
|
||||
|
||||
constructor(
|
||||
connection: Connection,
|
||||
page: Page,
|
||||
info: Bidi.BrowsingContext.Info,
|
||||
timeoutSettings: TimeoutSettings
|
||||
) {
|
||||
super();
|
||||
this.#connection = connection;
|
||||
this.#page = page;
|
||||
this._contextId = info.context;
|
||||
this._timeoutSettings = timeoutSettings;
|
||||
this.#handleFrameTree(info);
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#connection.on(event, subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
page(): Page {
|
||||
return this.#page;
|
||||
}
|
||||
|
||||
mainFrame(): Frame {
|
||||
const mainFrame = this._frameTree.getMainFrame();
|
||||
assert(mainFrame, 'Requesting main frame too early!');
|
||||
return mainFrame;
|
||||
}
|
||||
|
||||
frames(): Frame[] {
|
||||
return Array.from(this._frameTree.frames());
|
||||
}
|
||||
|
||||
frame(frameId: string): Frame | null {
|
||||
return this._frameTree.getById(frameId) || null;
|
||||
}
|
||||
|
||||
#handleFrameTree(info: Bidi.BrowsingContext.Info): void {
|
||||
if (info) {
|
||||
this.#onFrameAttached(info);
|
||||
}
|
||||
|
||||
if (!info.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const child of info.children) {
|
||||
this.#handleFrameTree(child);
|
||||
}
|
||||
}
|
||||
|
||||
#onFrameAttached(info: Bidi.BrowsingContext.Info): void {
|
||||
if (
|
||||
!this.frame(info.context) &&
|
||||
(this.frame(info.parent ?? '') || !this._frameTree.getMainFrame())
|
||||
) {
|
||||
const context = new Context(this.#connection, this, info);
|
||||
this.#connection.registerContext(context);
|
||||
|
||||
const frame = new Frame(this, context);
|
||||
this._frameTree.addFrame(frame);
|
||||
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
||||
}
|
||||
}
|
||||
|
||||
async #onFrameNavigated(
|
||||
info: Bidi.BrowsingContext.NavigationInfo
|
||||
): Promise<void> {
|
||||
const frameId = info.context;
|
||||
|
||||
let frame = this._frameTree.getById(frameId);
|
||||
// Detach all child frames first.
|
||||
if (frame) {
|
||||
for (const child of frame.childFrames()) {
|
||||
this.#removeFramesRecursively(child);
|
||||
}
|
||||
}
|
||||
|
||||
frame = await this._frameTree.waitForFrame(frameId);
|
||||
// frame._navigated(info);
|
||||
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
||||
}
|
||||
|
||||
#onFrameDetached(info: Bidi.BrowsingContext.Info): void {
|
||||
const frame = this.frame(info.context);
|
||||
|
||||
if (frame) {
|
||||
this.#removeFramesRecursively(frame);
|
||||
}
|
||||
}
|
||||
|
||||
#removeFramesRecursively(frame: Frame): void {
|
||||
for (const child of frame.childFrames()) {
|
||||
this.#removeFramesRecursively(child);
|
||||
}
|
||||
this.#connection.unregisterContext(frame._context);
|
||||
this._frameTree.removeFrame(frame);
|
||||
this.emit(FrameManagerEmittedEvents.FrameDetached, frame);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.removeAllListeners();
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#connection.off(event, subscriber);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,9 +27,11 @@ import {
|
||||
} from '../../api/Page.js';
|
||||
import {isErrorLike} from '../../util/ErrorLike.js';
|
||||
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js';
|
||||
import {Handler} from '../EventEmitter.js';
|
||||
import {EventType, Handler} from '../EventEmitter.js';
|
||||
import {FrameManagerEmittedEvents} from '../FrameManager.js';
|
||||
import {PDFOptions} from '../PDFOptions.js';
|
||||
import {Viewport} from '../PuppeteerViewport.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
import {
|
||||
debugError,
|
||||
@ -37,31 +39,77 @@ import {
|
||||
withSourcePuppeteerURLIfNone,
|
||||
} from '../util.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {Context, getBidiHandle} from './Context.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Page extends PageBase {
|
||||
#context: Context;
|
||||
_timeoutSettings = new TimeoutSettings();
|
||||
#connection: Connection;
|
||||
#frameManager: FrameManager;
|
||||
#viewport: Viewport | null = null;
|
||||
#closed = false;
|
||||
#subscribedEvents = new Map<string, Handler<any>>([
|
||||
['log.entryAdded', this.#onLogEntryAdded.bind(this)],
|
||||
['browsingContext.load', this.#onLoad.bind(this)],
|
||||
['browsingContext.domContentLoaded', this.#onDOMLoad.bind(this)],
|
||||
]) as Map<Bidi.Session.SubscribeParametersEvent, Handler>;
|
||||
#viewport: Viewport | null = null;
|
||||
]) as Map<Bidi.Session.SubscriptionRequestEvent, Handler>;
|
||||
#frameManagerEvents = new Map<EventType, Handler<any>>([
|
||||
[
|
||||
FrameManagerEmittedEvents.FrameAttached,
|
||||
frame => {
|
||||
return this.emit(PageEmittedEvents.FrameAttached, frame);
|
||||
},
|
||||
],
|
||||
[
|
||||
FrameManagerEmittedEvents.FrameDetached,
|
||||
frame => {
|
||||
return this.emit(PageEmittedEvents.FrameDetached, frame);
|
||||
},
|
||||
],
|
||||
[
|
||||
FrameManagerEmittedEvents.FrameNavigated,
|
||||
frame => {
|
||||
return this.emit(PageEmittedEvents.FrameNavigated, frame);
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
constructor(context: Context) {
|
||||
constructor(connection: Connection, info: Bidi.BrowsingContext.Info) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#connection = connection;
|
||||
|
||||
this.#context.connection
|
||||
this.#frameManager = new FrameManager(
|
||||
this.#connection,
|
||||
this,
|
||||
info,
|
||||
this._timeoutSettings
|
||||
);
|
||||
|
||||
for (const [event, subscriber] of this.#frameManagerEvents) {
|
||||
this.#frameManager.on(event, subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
static async _create(
|
||||
connection: Connection,
|
||||
info: Bidi.BrowsingContext.Info
|
||||
): Promise<Page> {
|
||||
const page = new Page(connection, info);
|
||||
|
||||
for (const [event, subscriber] of page.#subscribedEvents) {
|
||||
page.context().on(event, subscriber);
|
||||
}
|
||||
|
||||
await page.#connection
|
||||
.send('session.subscribe', {
|
||||
events: [
|
||||
...this.#subscribedEvents.keys(),
|
||||
] as Bidi.Session.SubscribeParameters['events'],
|
||||
contexts: [this.#context.id],
|
||||
events: [...page.#subscribedEvents.keys()],
|
||||
contexts: [info.context],
|
||||
})
|
||||
.catch(error => {
|
||||
if (isErrorLike(error) && !error.message.includes('Target closed')) {
|
||||
@ -69,15 +117,25 @@ export class Page extends PageBase {
|
||||
}
|
||||
});
|
||||
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#context.on(event, subscriber);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
override mainFrame(): Frame {
|
||||
return this.#frameManager.mainFrame();
|
||||
}
|
||||
|
||||
override frames(): Frame[] {
|
||||
return this.#frameManager.frames();
|
||||
}
|
||||
|
||||
context(): Context {
|
||||
return this.#frameManager.mainFrame()._context;
|
||||
}
|
||||
|
||||
#onLogEntryAdded(event: Bidi.Log.LogEntry): void {
|
||||
if (isConsoleLogEntry(event)) {
|
||||
const args = event.args.map(arg => {
|
||||
return getBidiHandle(this.#context, arg);
|
||||
return getBidiHandle(this.context(), arg);
|
||||
});
|
||||
|
||||
const text = args
|
||||
@ -134,18 +192,30 @@ export class Page extends PageBase {
|
||||
}
|
||||
|
||||
override async close(): Promise<void> {
|
||||
await this.#context.connection.send('session.unsubscribe', {
|
||||
events: [...this.#subscribedEvents.keys()],
|
||||
contexts: [this.#context.id],
|
||||
});
|
||||
|
||||
await this.#context.connection.send('browsingContext.close', {
|
||||
context: this.#context.id,
|
||||
});
|
||||
if (this.#closed) {
|
||||
return;
|
||||
}
|
||||
this.#closed = true;
|
||||
this.removeAllListeners();
|
||||
this.#frameManager.dispose();
|
||||
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#context.off(event, subscriber);
|
||||
this.context().off(event, subscriber);
|
||||
}
|
||||
|
||||
await this.#connection
|
||||
.send('session.unsubscribe', {
|
||||
events: [...this.#subscribedEvents.keys()],
|
||||
contexts: [this.context().id],
|
||||
})
|
||||
.catch(() => {
|
||||
// Suppress the error as we remove the context
|
||||
// after that anyway.
|
||||
});
|
||||
|
||||
await this.#connection.send('browsingContext.close', {
|
||||
context: this.context().id,
|
||||
});
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
@ -159,7 +229,7 @@ export class Page extends PageBase {
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
return this.#context.evaluateHandle(pageFunction, ...args);
|
||||
return this.mainFrame().evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
@ -173,7 +243,7 @@ export class Page extends PageBase {
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return this.#context.evaluate(pageFunction, ...args);
|
||||
return this.mainFrame().evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async goto(
|
||||
@ -183,26 +253,26 @@ export class Page extends PageBase {
|
||||
referrerPolicy?: string | undefined;
|
||||
}
|
||||
): Promise<HTTPResponse | null> {
|
||||
return this.#context.goto(url, options);
|
||||
return this.mainFrame().goto(url, options);
|
||||
}
|
||||
|
||||
override url(): string {
|
||||
return this.#context.url();
|
||||
return this.mainFrame().url();
|
||||
}
|
||||
|
||||
override setDefaultNavigationTimeout(timeout: number): void {
|
||||
this.#context._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
}
|
||||
|
||||
override setDefaultTimeout(timeout: number): void {
|
||||
this.#context._timeoutSettings.setDefaultTimeout(timeout);
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
override async setContent(
|
||||
html: string,
|
||||
options: WaitForOptions = {}
|
||||
): Promise<void> {
|
||||
await this.#context.setContent(html, options);
|
||||
await this.mainFrame().setContent(html, options);
|
||||
}
|
||||
|
||||
override async content(): Promise<string> {
|
||||
@ -226,7 +296,7 @@ export class Page extends PageBase {
|
||||
const deviceScaleFactor = 1;
|
||||
const screenOrientation = {angle: 0, type: 'portraitPrimary'};
|
||||
|
||||
await this.#context.sendCDPCommand('Emulation.setDeviceMetricsOverride', {
|
||||
await this.context().sendCDPCommand('Emulation.setDeviceMetricsOverride', {
|
||||
mobile,
|
||||
width,
|
||||
height,
|
||||
@ -255,8 +325,8 @@ export class Page extends PageBase {
|
||||
timeout,
|
||||
} = this._getPDFOptions(options, 'cm');
|
||||
const {result} = await waitWithTimeout(
|
||||
this.#context.connection.send('browsingContext.print', {
|
||||
context: this.#context._contextId,
|
||||
this.#connection.send('browsingContext.print', {
|
||||
context: this.context().id,
|
||||
background,
|
||||
margin,
|
||||
orientation: landscape ? 'landscape' : 'portrait',
|
||||
@ -310,10 +380,10 @@ export class Page extends PageBase {
|
||||
throw new Error('BiDi only supports "encoding" and "path" options');
|
||||
}
|
||||
|
||||
const {result} = await this.#context.connection.send(
|
||||
const {result} = await this.#connection.send(
|
||||
'browsingContext.captureScreenshot',
|
||||
{
|
||||
context: this.#context._contextId,
|
||||
context: this.context().id,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -31,7 +31,7 @@ class UnserializableError extends Error {}
|
||||
* @internal
|
||||
*/
|
||||
export class BidiSerializer {
|
||||
static serializeNumber(arg: number): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
static serializeNumber(arg: number): Bidi.CommonDataTypes.LocalValue {
|
||||
let value: Bidi.CommonDataTypes.SpecialNumber | number;
|
||||
if (Object.is(arg, -0)) {
|
||||
value = '-0';
|
||||
@ -50,9 +50,7 @@ export class BidiSerializer {
|
||||
};
|
||||
}
|
||||
|
||||
static serializeObject(
|
||||
arg: object | null
|
||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
static serializeObject(arg: object | null): Bidi.CommonDataTypes.LocalValue {
|
||||
if (arg === null) {
|
||||
return {
|
||||
type: 'null',
|
||||
@ -111,9 +109,7 @@ export class BidiSerializer {
|
||||
);
|
||||
}
|
||||
|
||||
static serializeRemoveValue(
|
||||
arg: unknown
|
||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
static serializeRemoveValue(arg: unknown): Bidi.CommonDataTypes.LocalValue {
|
||||
switch (typeof arg) {
|
||||
case 'symbol':
|
||||
case 'function':
|
||||
@ -148,7 +144,7 @@ export class BidiSerializer {
|
||||
static serialize(
|
||||
arg: unknown,
|
||||
context: Context
|
||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
): Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue {
|
||||
// TODO: See use case of LazyArgs
|
||||
const objectHandle =
|
||||
arg && (arg instanceof JSHandle || arg instanceof ElementHandle)
|
||||
|
@ -38,7 +38,7 @@ export async function releaseReference(
|
||||
}
|
||||
await client.connection
|
||||
.send('script.disown', {
|
||||
target: {context: client._contextId},
|
||||
target: {context: client.id},
|
||||
handles: [remoteReference.handle],
|
||||
})
|
||||
.catch((error: any) => {
|
||||
|
@ -38,7 +38,6 @@ export * from './ExecutionContext.js';
|
||||
export * from './fetch.js';
|
||||
export * from './FileChooser.js';
|
||||
export * from './FirefoxTargetManager.js';
|
||||
export * from './Frame.js';
|
||||
export * from './FrameManager.js';
|
||||
export * from './FrameTree.js';
|
||||
export * from './Input.js';
|
||||
|
@ -71,6 +71,12 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.Events.Load *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.Events.PageError *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -143,12 +149,6 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should execute after cross-site navigation",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have correct execution contexts",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -245,6 +245,48 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should not send attach/detach events for main frame",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should persist mainFrame on cross-process navigation",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should report frame from-inside shadow DOM",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should support url fragment",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame.evaluate allows readonly array to be an argument",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame.evaluateHandle should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[frame.spec] Frame specs Frame.page should retrieve the page from a frame",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[headful.spec] *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -2252,7 +2294,13 @@
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
||||
"platforms": ["linux"],
|
||||
"parameters": ["cdp", "chrome", "headless", "webDriverBiDi"],
|
||||
"parameters": ["chrome", "headless"],
|
||||
"expectations": ["PASS", "TIMEOUT"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation \"after each\" hook in \"navigation\"",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "headless", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
}
|
||||
]
|
||||
|
@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import {Frame} from 'puppeteer-core/internal/api/Frame.js';
|
||||
import {CDPSession} from 'puppeteer-core/internal/common/Connection.js';
|
||||
import {Frame} from 'puppeteer-core/internal/common/Frame.js';
|
||||
|
||||
import {
|
||||
getTestState,
|
||||
|
@ -30,7 +30,7 @@ import {
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
} from './mocha-utils.js';
|
||||
import {attachFrame, detachFrame, waitEvent} from './utils.js';
|
||||
import {attachFrame, detachFrame, isFavicon, waitEvent} from './utils.js';
|
||||
|
||||
describe('Page', function () {
|
||||
setupTestBrowserHooks();
|
||||
@ -135,7 +135,7 @@ describe('Page', function () {
|
||||
const handler = sinon.spy();
|
||||
const onResponse = (response: {url: () => string}) => {
|
||||
// Ignore default favicon requests.
|
||||
if (!response.url().endsWith('favicon.ico')) {
|
||||
if (!isFavicon(response)) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
@ -158,7 +158,7 @@ describe('Page', function () {
|
||||
const handler = sinon.spy();
|
||||
const onResponse = (response: {url: () => string}) => {
|
||||
// Ignore default favicon requests.
|
||||
if (!response.url().endsWith('favicon.ico')) {
|
||||
if (!isFavicon(response)) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
@ -17,9 +17,9 @@
|
||||
import path from 'path';
|
||||
|
||||
import expect from 'expect';
|
||||
import {Frame} from 'puppeteer-core/internal/api/Frame.js';
|
||||
import {Page} from 'puppeteer-core/internal/api/Page.js';
|
||||
import {EventEmitter} from 'puppeteer-core/internal/common/EventEmitter.js';
|
||||
import {Frame} from 'puppeteer-core/internal/common/Frame.js';
|
||||
|
||||
import {compare} from './golden-utils.js';
|
||||
|
||||
|
@ -119,6 +119,7 @@ async function main() {
|
||||
? {
|
||||
DEBUG: 'puppeteer:*',
|
||||
EXTRA_LAUNCH_OPTIONS: JSON.stringify({
|
||||
dumpio: true,
|
||||
extraPrefsFirefox: {
|
||||
'remote.log.level': 'Trace',
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user