mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
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:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
- macos-latest
|
# Disabled as BiDi has issue on mac https://bugzilla.mozilla.org/show_bug.cgi?id=1832778
|
||||||
|
# - macos-latest
|
||||||
suite:
|
suite:
|
||||||
- firefox-bidi
|
- firefox-bidi
|
||||||
- firefox-headful
|
- firefox-headful
|
||||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -2710,9 +2710,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi": {
|
"node_modules/chromium-bidi": {
|
||||||
"version": "0.4.7",
|
"version": "0.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
|
||||||
"integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
|
"integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mitt": "3.0.0"
|
"mitt": "3.0.0"
|
||||||
},
|
},
|
||||||
@ -9464,7 +9464,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "1.2.0",
|
"@puppeteer/browsers": "1.2.0",
|
||||||
"chromium-bidi": "0.4.7",
|
"chromium-bidi": "0.4.9",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1120988",
|
"devtools-protocol": "0.0.1120988",
|
||||||
@ -11441,9 +11441,9 @@
|
|||||||
"version": "1.1.4"
|
"version": "1.1.4"
|
||||||
},
|
},
|
||||||
"chromium-bidi": {
|
"chromium-bidi": {
|
||||||
"version": "0.4.7",
|
"version": "0.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz",
|
||||||
"integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
|
"integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"mitt": "3.0.0"
|
"mitt": "3.0.0"
|
||||||
}
|
}
|
||||||
@ -14503,7 +14503,7 @@
|
|||||||
"version": "file:packages/puppeteer-core",
|
"version": "file:packages/puppeteer-core",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@puppeteer/browsers": "1.2.0",
|
"@puppeteer/browsers": "1.2.0",
|
||||||
"chromium-bidi": "0.4.7",
|
"chromium-bidi": "0.4.9",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1120988",
|
"devtools-protocol": "0.0.1120988",
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
"author": "The Chromium Authors",
|
"author": "The Chromium Authors",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chromium-bidi": "0.4.7",
|
"chromium-bidi": "0.4.9",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1120988",
|
"devtools-protocol": "0.0.1120988",
|
||||||
|
@ -18,7 +18,6 @@ import {Protocol} from 'devtools-protocol';
|
|||||||
|
|
||||||
import {CDPSession} from '../common/Connection.js';
|
import {CDPSession} from '../common/Connection.js';
|
||||||
import {ExecutionContext} from '../common/ExecutionContext.js';
|
import {ExecutionContext} from '../common/ExecutionContext.js';
|
||||||
import {Frame} from '../common/Frame.js';
|
|
||||||
import {MouseClickOptions} from '../common/Input.js';
|
import {MouseClickOptions} from '../common/Input.js';
|
||||||
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||||
import {
|
import {
|
||||||
@ -30,6 +29,7 @@ import {
|
|||||||
} from '../common/types.js';
|
} from '../common/types.js';
|
||||||
import {KeyInput} from '../common/USKeyboardLayout.js';
|
import {KeyInput} from '../common/USKeyboardLayout.js';
|
||||||
|
|
||||||
|
import {Frame} from './Frame.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {ScreenshotOptions} from './Page.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 {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
import {CDPSession} from '../common/Connection.js';
|
import {CDPSession} from '../common/Connection.js';
|
||||||
import {Frame} from '../common/Frame.js';
|
|
||||||
|
|
||||||
|
import {Frame} from './Frame.js';
|
||||||
import {HTTPResponse} from './HTTPResponse.js';
|
import {HTTPResponse} from './HTTPResponse.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
import Protocol from 'devtools-protocol';
|
import Protocol from 'devtools-protocol';
|
||||||
|
|
||||||
import {Frame} from '../common/Frame.js';
|
|
||||||
import {SecurityDetails} from '../common/SecurityDetails.js';
|
import {SecurityDetails} from '../common/SecurityDetails.js';
|
||||||
|
|
||||||
|
import {Frame} from './Frame.js';
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,12 +28,6 @@ import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
|||||||
import type {Dialog} from '../common/Dialog.js';
|
import type {Dialog} from '../common/Dialog.js';
|
||||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||||
import type {FileChooser} from '../common/FileChooser.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 {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
|
||||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||||
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.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 {Browser} from './Browser.js';
|
||||||
import type {BrowserContext} from './BrowserContext.js';
|
import type {BrowserContext} from './BrowserContext.js';
|
||||||
import type {ClickOptions, ElementHandle} from './ElementHandle.js';
|
import type {ClickOptions, ElementHandle} from './ElementHandle.js';
|
||||||
|
import type {
|
||||||
|
Frame,
|
||||||
|
FrameAddScriptTagOptions,
|
||||||
|
FrameAddStyleTagOptions,
|
||||||
|
FrameWaitForFunctionOptions,
|
||||||
|
} from './Frame.js';
|
||||||
import type {JSHandle} from './JSHandle.js';
|
import type {JSHandle} from './JSHandle.js';
|
||||||
import {Locator} from './Locator.js';
|
import {Locator} from './Locator.js';
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ export * from './BrowserContext.js';
|
|||||||
export * from './Page.js';
|
export * from './Page.js';
|
||||||
export * from './JSHandle.js';
|
export * from './JSHandle.js';
|
||||||
export * from './ElementHandle.js';
|
export * from './ElementHandle.js';
|
||||||
|
export * from './Frame.js';
|
||||||
export * from './HTTPResponse.js';
|
export * from './HTTPResponse.js';
|
||||||
export * from './HTTPRequest.js';
|
export * from './HTTPRequest.js';
|
||||||
export * from './Locator.js';
|
export * from './Locator.js';
|
||||||
|
@ -253,7 +253,7 @@ export class CDPElementHandle<
|
|||||||
|
|
||||||
override async contentFrame(): Promise<Frame | null> {
|
override async contentFrame(): Promise<Frame | null> {
|
||||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.id,
|
||||||
});
|
});
|
||||||
if (typeof nodeInfo.node.frameId !== 'string') {
|
if (typeof nodeInfo.node.frameId !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@ -268,7 +268,7 @@ export class CDPElementHandle<
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugError(error);
|
debugError(error);
|
||||||
@ -333,7 +333,7 @@ export class CDPElementHandle<
|
|||||||
const [result, layoutMetrics] = await Promise.all([
|
const [result, layoutMetrics] = await Promise.all([
|
||||||
this.client
|
this.client
|
||||||
.send('DOM.getContentQuads', {
|
.send('DOM.getContentQuads', {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.id,
|
||||||
})
|
})
|
||||||
.catch(debugError),
|
.catch(debugError),
|
||||||
(this.#page as CDPPage)._client().send('Page.getLayoutMetrics'),
|
(this.#page as CDPPage)._client().send('Page.getLayoutMetrics'),
|
||||||
@ -583,9 +583,8 @@ export class CDPElementHandle<
|
|||||||
return path.resolve(filePath);
|
return path.resolve(filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const {objectId} = this.remoteObject();
|
|
||||||
const {node} = await this.client.send('DOM.describeNode', {
|
const {node} = await this.client.send('DOM.describeNode', {
|
||||||
objectId,
|
objectId: this.id,
|
||||||
});
|
});
|
||||||
const {backendNodeId} = node;
|
const {backendNodeId} = node;
|
||||||
|
|
||||||
@ -603,7 +602,7 @@ export class CDPElementHandle<
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.client.send('DOM.setFileInputFiles', {
|
await this.client.send('DOM.setFileInputFiles', {
|
||||||
objectId,
|
objectId: this.id,
|
||||||
files,
|
files,
|
||||||
backendNodeId,
|
backendNodeId,
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,12 @@
|
|||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
import {type ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
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 {HTTPResponse} from '../api/HTTPResponse.js';
|
||||||
import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
||||||
import {assert} from '../util/assert.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';
|
import {importFSPromises, withSourcePuppeteerURLIfNone} from './util.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @internal
|
||||||
*/
|
*/
|
||||||
export interface FrameWaitForFunctionOptions {
|
export class Frame extends BaseFrame {
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
#url = '';
|
#url = '';
|
||||||
#detached = false;
|
#detached = false;
|
||||||
#client!: CDPSession;
|
#client!: CDPSession;
|
||||||
|
|
||||||
/**
|
override worlds!: IsolatedWorldChart;
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
worlds!: IsolatedWorldChart;
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_frameManager: FrameManager;
|
_frameManager: FrameManager;
|
||||||
/**
|
override _id: string;
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_id: string;
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_loaderId = '';
|
_loaderId = '';
|
||||||
/**
|
override _name?: string;
|
||||||
* @internal
|
override _hasStartedLoading = false;
|
||||||
*/
|
|
||||||
_name?: string;
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_hasStartedLoading = false;
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_lifecycleEvents = new Set<string>();
|
_lifecycleEvents = new Set<string>();
|
||||||
/**
|
override _parentId?: string;
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_parentId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
constructor(
|
constructor(
|
||||||
frameManager: FrameManager,
|
frameManager: FrameManager,
|
||||||
frameId: string,
|
frameId: string,
|
||||||
parentFrameId: string | undefined,
|
parentFrameId: string | undefined,
|
||||||
client: CDPSession
|
client: CDPSession
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
this.#url = '';
|
this.#url = '';
|
||||||
this._id = frameId;
|
this._id = frameId;
|
||||||
@ -232,9 +82,6 @@ export class Frame {
|
|||||||
this.updateClient(client);
|
this.updateClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
updateClient(client: CDPSession): void {
|
updateClient(client: CDPSession): void {
|
||||||
this.#client = client;
|
this.#client = client;
|
||||||
this.worlds = {
|
this.worlds = {
|
||||||
@ -243,59 +90,15 @@ export class Frame {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override page(): Page {
|
||||||
* The page associated with the frame.
|
|
||||||
*/
|
|
||||||
page(): Page {
|
|
||||||
return this._frameManager.page();
|
return this._frameManager.page();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override isOOPFrame(): boolean {
|
||||||
* Is `true` if the frame is an out-of-process (OOP) frame. Otherwise,
|
|
||||||
* `false`.
|
|
||||||
*/
|
|
||||||
isOOPFrame(): boolean {
|
|
||||||
return this.#client !== this._frameManager.client;
|
return this.#client !== this._frameManager.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async goto(
|
||||||
* 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,
|
url: string,
|
||||||
options: {
|
options: {
|
||||||
referer?: string;
|
referer?: string;
|
||||||
@ -378,30 +181,7 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async waitForNavigation(
|
||||||
* 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: {
|
options: {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
||||||
@ -432,27 +212,15 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override _client(): CDPSession {
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_client(): CDPSession {
|
|
||||||
return this.#client;
|
return this.#client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override executionContext(): Promise<ExecutionContext> {
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
executionContext(): Promise<ExecutionContext> {
|
|
||||||
return this.worlds[MAIN_WORLD].executionContext();
|
return this.worlds[MAIN_WORLD].executionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async evaluateHandle<
|
||||||
* 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[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
>(
|
>(
|
||||||
@ -466,13 +234,7 @@ export class Frame {
|
|||||||
return this.worlds[MAIN_WORLD].evaluateHandle(pageFunction, ...args);
|
return this.worlds[MAIN_WORLD].evaluateHandle(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async evaluate<
|
||||||
* 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[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
>(
|
>(
|
||||||
@ -486,53 +248,19 @@ export class Frame {
|
|||||||
return this.worlds[MAIN_WORLD].evaluate(pageFunction, ...args);
|
return this.worlds[MAIN_WORLD].evaluate(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async $<Selector extends string>(
|
||||||
* 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
|
selector: Selector
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
return this.worlds[MAIN_WORLD].$(selector);
|
return this.worlds[MAIN_WORLD].$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async $$<Selector extends string>(
|
||||||
* 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
|
selector: Selector
|
||||||
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
|
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
|
||||||
return this.worlds[MAIN_WORLD].$$(selector);
|
return this.worlds[MAIN_WORLD].$$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async $eval<
|
||||||
* 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,
|
Selector extends string,
|
||||||
Params extends unknown[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||||
@ -548,27 +276,7 @@ export class Frame {
|
|||||||
return this.worlds[MAIN_WORLD].$eval(selector, pageFunction, ...args);
|
return this.worlds[MAIN_WORLD].$eval(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async $$eval<
|
||||||
* 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,
|
Selector extends string,
|
||||||
Params extends unknown[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFuncWith<
|
Func extends EvaluateFuncWith<
|
||||||
@ -584,56 +292,11 @@ export class Frame {
|
|||||||
return this.worlds[MAIN_WORLD].$$eval(selector, pageFunction, ...args);
|
return this.worlds[MAIN_WORLD].$$eval(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||||
* @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>>> {
|
|
||||||
return this.worlds[MAIN_WORLD].$x(expression);
|
return this.worlds[MAIN_WORLD].$x(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async waitForSelector<Selector extends string>(
|
||||||
* 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,
|
selector: Selector,
|
||||||
options: WaitForSelectorOptions = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
@ -646,29 +309,7 @@ export class Frame {
|
|||||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async waitForXPath(
|
||||||
* @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,
|
xpath: string,
|
||||||
options: WaitForSelectorOptions = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<Node> | null> {
|
): Promise<ElementHandle<Node> | null> {
|
||||||
@ -678,40 +319,7 @@ export class Frame {
|
|||||||
return this.waitForSelector(`xpath/${xpath}`, options);
|
return this.waitForSelector(`xpath/${xpath}`, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override waitForFunction<
|
||||||
* @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[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
>(
|
>(
|
||||||
@ -726,21 +334,11 @@ export class Frame {
|
|||||||
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async content(): Promise<string> {
|
||||||
* The full HTML contents of the frame, including the DOCTYPE.
|
|
||||||
*/
|
|
||||||
async content(): Promise<string> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].content();
|
return this.worlds[PUPPETEER_WORLD].content();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async setContent(
|
||||||
* 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,
|
html: string,
|
||||||
options: {
|
options: {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
@ -750,56 +348,27 @@ export class Frame {
|
|||||||
return this.worlds[PUPPETEER_WORLD].setContent(html, options);
|
return this.worlds[PUPPETEER_WORLD].setContent(html, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override name(): string {
|
||||||
* 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 || '';
|
return this._name || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override url(): string {
|
||||||
* The frame's URL.
|
|
||||||
*/
|
|
||||||
url(): string {
|
|
||||||
return this.#url;
|
return this.#url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override parentFrame(): Frame | null {
|
||||||
* The parent frame, if any. Detached and main frames return `null`.
|
|
||||||
*/
|
|
||||||
parentFrame(): Frame | null {
|
|
||||||
return this._frameManager._frameTree.parentFrame(this._id) || null;
|
return this._frameManager._frameTree.parentFrame(this._id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override childFrames(): Frame[] {
|
||||||
* An array of child frames.
|
|
||||||
*/
|
|
||||||
childFrames(): Frame[] {
|
|
||||||
return this._frameManager._frameTree.childFrames(this._id);
|
return this._frameManager._frameTree.childFrames(this._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override isDetached(): boolean {
|
||||||
* Is`true` if the frame has been detached. Otherwise, `false`.
|
|
||||||
*/
|
|
||||||
isDetached(): boolean {
|
|
||||||
return this.#detached;
|
return this.#detached;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async addScriptTag(
|
||||||
* 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
|
options: FrameAddScriptTagOptions
|
||||||
): Promise<ElementHandle<HTMLScriptElement>> {
|
): Promise<ElementHandle<HTMLScriptElement>> {
|
||||||
let {content = '', type} = options;
|
let {content = '', type} = options;
|
||||||
@ -861,20 +430,13 @@ export class Frame {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async addStyleTag(
|
||||||
* 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'>
|
options: Omit<FrameAddStyleTagOptions, 'url'>
|
||||||
): Promise<ElementHandle<HTMLStyleElement>>;
|
): Promise<ElementHandle<HTMLStyleElement>>;
|
||||||
async addStyleTag(
|
override async addStyleTag(
|
||||||
options: FrameAddStyleTagOptions
|
options: FrameAddStyleTagOptions
|
||||||
): Promise<ElementHandle<HTMLLinkElement>>;
|
): Promise<ElementHandle<HTMLLinkElement>>;
|
||||||
async addStyleTag(
|
override async addStyleTag(
|
||||||
options: FrameAddStyleTagOptions
|
options: FrameAddStyleTagOptions
|
||||||
): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> {
|
): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> {
|
||||||
let {content = ''} = options;
|
let {content = ''} = options;
|
||||||
@ -937,106 +499,30 @@ export class Frame {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async click(
|
||||||
* 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,
|
selector: string,
|
||||||
options: Readonly<ClickOptions> = {}
|
options: Readonly<ClickOptions> = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.worlds[PUPPETEER_WORLD].click(selector, options);
|
return this.worlds[PUPPETEER_WORLD].click(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async focus(selector: string): Promise<void> {
|
||||||
* 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> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].focus(selector);
|
return this.worlds[PUPPETEER_WORLD].focus(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async hover(selector: string): Promise<void> {
|
||||||
* 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> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].hover(selector);
|
return this.worlds[PUPPETEER_WORLD].hover(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override select(selector: string, ...values: string[]): Promise<string[]> {
|
||||||
* 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[]> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].select(selector, ...values);
|
return this.worlds[PUPPETEER_WORLD].select(selector, ...values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async tap(selector: string): Promise<void> {
|
||||||
* 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> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].tap(selector);
|
return this.worlds[PUPPETEER_WORLD].tap(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async type(
|
||||||
* 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,
|
selector: string,
|
||||||
text: string,
|
text: string,
|
||||||
options?: {delay: number}
|
options?: {delay: number}
|
||||||
@ -1044,42 +530,16 @@ export class Frame {
|
|||||||
return this.worlds[PUPPETEER_WORLD].type(selector, text, options);
|
return this.worlds[PUPPETEER_WORLD].type(selector, text, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override waitForTimeout(milliseconds: number): Promise<void> {
|
||||||
* @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 => {
|
return new Promise(resolve => {
|
||||||
setTimeout(resolve, milliseconds);
|
setTimeout(resolve, milliseconds);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override async title(): Promise<string> {
|
||||||
* The frame's title.
|
|
||||||
*/
|
|
||||||
async title(): Promise<string> {
|
|
||||||
return this.worlds[PUPPETEER_WORLD].title();
|
return this.worlds[PUPPETEER_WORLD].title();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_deviceRequestPromptManager(): DeviceRequestPromptManager {
|
_deviceRequestPromptManager(): DeviceRequestPromptManager {
|
||||||
if (this.isOOPFrame()) {
|
if (this.isOOPFrame()) {
|
||||||
return this._frameManager._deviceRequestPromptManager(this.#client);
|
return this._frameManager._deviceRequestPromptManager(this.#client);
|
||||||
@ -1089,53 +549,21 @@ export class Frame {
|
|||||||
return parentFrame._deviceRequestPromptManager();
|
return parentFrame._deviceRequestPromptManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override waitForDevicePrompt(
|
||||||
* 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 = {}
|
options: WaitTimeoutOptions = {}
|
||||||
): Promise<DeviceRequestPrompt> {
|
): Promise<DeviceRequestPrompt> {
|
||||||
return this._deviceRequestPromptManager().waitForDevicePrompt(options);
|
return this._deviceRequestPromptManager().waitForDevicePrompt(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_navigated(framePayload: Protocol.Page.Frame): void {
|
_navigated(framePayload: Protocol.Page.Frame): void {
|
||||||
this._name = framePayload.name;
|
this._name = framePayload.name;
|
||||||
this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`;
|
this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_navigatedWithinDocument(url: string): void {
|
_navigatedWithinDocument(url: string): void {
|
||||||
this.#url = url;
|
this.#url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_onLifecycleEvent(loaderId: string, name: string): void {
|
_onLifecycleEvent(loaderId: string, name: string): void {
|
||||||
if (name === 'init') {
|
if (name === 'init') {
|
||||||
this._loaderId = loaderId;
|
this._loaderId = loaderId;
|
||||||
@ -1144,24 +572,15 @@ export class Frame {
|
|||||||
this._lifecycleEvents.add(name);
|
this._lifecycleEvents.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_onLoadingStopped(): void {
|
_onLoadingStopped(): void {
|
||||||
this._lifecycleEvents.add('DOMContentLoaded');
|
this._lifecycleEvents.add('DOMContentLoaded');
|
||||||
this._lifecycleEvents.add('load');
|
this._lifecycleEvents.add('load');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_onLoadingStarted(): void {
|
_onLoadingStarted(): void {
|
||||||
this._hasStartedLoading = true;
|
this._hasStartedLoading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_detach(): void {
|
_detach(): void {
|
||||||
this.#detached = true;
|
this.#detached = true;
|
||||||
this.worlds[MAIN_WORLD]._detach();
|
this.worlds[MAIN_WORLD]._detach();
|
||||||
|
@ -25,6 +25,7 @@ import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js';
|
|||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
import {Frame} from './Frame.js';
|
import {Frame} from './Frame.js';
|
||||||
|
import {Frame as CDPFrame} from './Frame.js';
|
||||||
import {FrameTree} from './FrameTree.js';
|
import {FrameTree} from './FrameTree.js';
|
||||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||||
@ -69,7 +70,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_frameTree = new FrameTree();
|
_frameTree = new FrameTree<Frame>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of frame IDs stored to indicate if a frame has received a
|
* Set of frame IDs stored to indicate if a frame has received a
|
||||||
@ -305,7 +306,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame = new Frame(this, frameId, parentFrameId, session);
|
frame = new CDPFrame(this, frameId, parentFrameId, session);
|
||||||
this._frameTree.addFrame(frame);
|
this._frameTree.addFrame(frame);
|
||||||
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
||||||
}
|
}
|
||||||
@ -331,7 +332,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
frame._id = frameId;
|
frame._id = frameId;
|
||||||
} else {
|
} else {
|
||||||
// Initial main frame navigation.
|
// Initial main frame navigation.
|
||||||
frame = new Frame(this, frameId, undefined, this.#client);
|
frame = new CDPFrame(this, frameId, undefined, this.#client);
|
||||||
}
|
}
|
||||||
this._frameTree.addFrame(frame);
|
this._frameTree.addFrame(frame);
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Frame as BaseFrame} from '../api/Frame.js';
|
||||||
import {
|
import {
|
||||||
createDeferredPromise,
|
createDeferredPromise,
|
||||||
DeferredPromise,
|
DeferredPromise,
|
||||||
} from '../util/DeferredPromise.js';
|
} from '../util/DeferredPromise.js';
|
||||||
|
|
||||||
import type {Frame} from './Frame.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps track of the page frame tree and it's is managed by
|
* Keeps track of the page frame tree and it's is managed by
|
||||||
* {@link FrameManager}. FrameTree uses frame IDs to reference frame and it
|
* {@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.
|
* structure is eventually consistent.
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class FrameTree {
|
export class FrameTree<Frame extends BaseFrame> {
|
||||||
#frames = new Map<string, Frame>();
|
#frames = new Map<string, Frame>();
|
||||||
// frameID -> parentFrameID
|
// frameID -> parentFrameID
|
||||||
#parentIds = new Map<string, string>();
|
#parentIds = new Map<string, string>();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
|
import {Frame} from '../api/Frame.js';
|
||||||
import {
|
import {
|
||||||
ContinueRequestOverrides,
|
ContinueRequestOverrides,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
@ -31,7 +32,6 @@ import {assert} from '../util/assert.js';
|
|||||||
|
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {ProtocolError} from './Errors.js';
|
import {ProtocolError} from './Errors.js';
|
||||||
import {Frame} from './Frame.js';
|
|
||||||
import {debugError, isString} from './util.js';
|
import {debugError, isString} from './util.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
|
import {Frame} from '../api/Frame.js';
|
||||||
import {
|
import {
|
||||||
HTTPResponse as BaseHTTPResponse,
|
HTTPResponse as BaseHTTPResponse,
|
||||||
RemoteAddress,
|
RemoteAddress,
|
||||||
@ -23,7 +24,6 @@ import {createDeferredPromise} from '../util/DeferredPromise.js';
|
|||||||
|
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {ProtocolError} from './Errors.js';
|
import {ProtocolError} from './Errors.js';
|
||||||
import {Frame} from './Frame.js';
|
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
import {SecurityDetails} from './SecurityDetails.js';
|
import {SecurityDetails} from './SecurityDetails.js';
|
||||||
|
|
||||||
|
@ -21,6 +21,12 @@ import {Protocol} from 'devtools-protocol';
|
|||||||
import type {Browser} from '../api/Browser.js';
|
import type {Browser} from '../api/Browser.js';
|
||||||
import type {BrowserContext} from '../api/BrowserContext.js';
|
import type {BrowserContext} from '../api/BrowserContext.js';
|
||||||
import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
|
||||||
|
import {
|
||||||
|
Frame,
|
||||||
|
FrameAddScriptTagOptions,
|
||||||
|
FrameAddStyleTagOptions,
|
||||||
|
FrameWaitForFunctionOptions,
|
||||||
|
} from '../api/Frame.js';
|
||||||
import {HTTPRequest} from '../api/HTTPRequest.js';
|
import {HTTPRequest} from '../api/HTTPRequest.js';
|
||||||
import {HTTPResponse} from '../api/HTTPResponse.js';
|
import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||||
import {JSHandle} from '../api/JSHandle.js';
|
import {JSHandle} from '../api/JSHandle.js';
|
||||||
@ -55,12 +61,6 @@ import {DeviceRequestPrompt} from './DeviceRequestPrompt.js';
|
|||||||
import {Dialog} from './Dialog.js';
|
import {Dialog} from './Dialog.js';
|
||||||
import {EmulationManager} from './EmulationManager.js';
|
import {EmulationManager} from './EmulationManager.js';
|
||||||
import {FileChooser} from './FileChooser.js';
|
import {FileChooser} from './FileChooser.js';
|
||||||
import {
|
|
||||||
Frame,
|
|
||||||
FrameAddScriptTagOptions,
|
|
||||||
FrameAddStyleTagOptions,
|
|
||||||
FrameWaitForFunctionOptions,
|
|
||||||
} from './Frame.js';
|
|
||||||
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||||
import {Keyboard, Mouse, Touchscreen} from './Input.js';
|
import {Keyboard, Mouse, Touchscreen} from './Input.js';
|
||||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||||
@ -895,21 +895,21 @@ export class CDPPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async content(): Promise<string> {
|
override async content(): Promise<string> {
|
||||||
return await this.#frameManager.mainFrame().content();
|
return await this.mainFrame().content();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async setContent(
|
override async setContent(
|
||||||
html: string,
|
html: string,
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#frameManager.mainFrame().setContent(html, options);
|
await this.mainFrame().setContent(html, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async goto(
|
override async goto(
|
||||||
url: string,
|
url: string,
|
||||||
options: WaitForOptions & {referer?: string; referrerPolicy?: string} = {}
|
options: WaitForOptions & {referer?: string; referrerPolicy?: string} = {}
|
||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
return await this.#frameManager.mainFrame().goto(url, options);
|
return await this.mainFrame().goto(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async reload(
|
override async reload(
|
||||||
@ -926,7 +926,7 @@ export class CDPPage extends Page {
|
|||||||
override async waitForNavigation(
|
override async waitForNavigation(
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
return await this.#frameManager.mainFrame().waitForNavigation(options);
|
return await this.mainFrame().waitForNavigation(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sessionClosePromise(): Promise<Error> {
|
#sessionClosePromise(): Promise<Error> {
|
||||||
@ -1163,7 +1163,7 @@ export class CDPPage extends Page {
|
|||||||
'Throttling rate should be greater or equal to 1'
|
'Throttling rate should be greater or equal to 1'
|
||||||
);
|
);
|
||||||
await this.#client.send('Emulation.setCPUThrottlingRate', {
|
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,
|
this.evaluate.name,
|
||||||
pageFunction
|
pageFunction
|
||||||
);
|
);
|
||||||
return this.#frameManager.mainFrame().evaluate(pageFunction, ...args);
|
return this.mainFrame().evaluate(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async evaluateOnNewDocument<
|
override async evaluateOnNewDocument<
|
||||||
|
@ -15,12 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ElementHandle} from '../api/ElementHandle.js';
|
import {ElementHandle} from '../api/ElementHandle.js';
|
||||||
|
import type {Frame} from '../api/Frame.js';
|
||||||
import type PuppeteerUtil from '../injected/injected.js';
|
import type PuppeteerUtil from '../injected/injected.js';
|
||||||
import {assert} from '../util/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
import {interpolateFunction, stringifyFunction} from '../util/Function.js';
|
import {interpolateFunction, stringifyFunction} from '../util/Function.js';
|
||||||
|
|
||||||
import type {Frame} from './Frame.js';
|
|
||||||
import {transposeIterableHandle} from './HandleIterator.js';
|
import {transposeIterableHandle} from './HandleIterator.js';
|
||||||
import type {WaitForSelectorOptions} from './IsolatedWorld.js';
|
import type {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||||
|
@ -33,16 +33,18 @@ import {Connection} from './Connection.js';
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class Browser extends BrowserBase {
|
export class Browser extends BrowserBase {
|
||||||
|
static readonly subscribeModules = ['browsingContext'];
|
||||||
|
|
||||||
static async create(opts: Options): Promise<Browser> {
|
static async create(opts: Options): Promise<Browser> {
|
||||||
// TODO: await until the connection is established.
|
// TODO: await until the connection is established.
|
||||||
try {
|
try {
|
||||||
await opts.connection.send('session.new', {});
|
await opts.connection.send('session.new', {});
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
await opts.connection.send('session.subscribe', {
|
await opts.connection.send('session.subscribe', {
|
||||||
events: [
|
events: Browser.subscribeModules as Bidi.Message.EventNames[],
|
||||||
'browsingContext.contextCreated',
|
|
||||||
] as Bidi.Session.SubscribeParametersEvent[],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Browser(opts);
|
return new Browser(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,13 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||||
import {Page as PageBase} from '../../api/Page.js';
|
import {Page as PageBase} from '../../api/Page.js';
|
||||||
import {Viewport} from '../PuppeteerViewport.js';
|
import {Viewport} from '../PuppeteerViewport.js';
|
||||||
|
|
||||||
import {Connection} from './Connection.js';
|
import {Connection} from './Connection.js';
|
||||||
import {Context} from './Context.js';
|
|
||||||
import {Page} from './Page.js';
|
import {Page} from './Page.js';
|
||||||
|
import {debugError} from './utils.js';
|
||||||
|
|
||||||
interface BrowserContextOptions {
|
interface BrowserContextOptions {
|
||||||
defaultViewport: Viewport | null;
|
defaultViewport: Viewport | null;
|
||||||
@ -32,19 +34,34 @@ interface BrowserContextOptions {
|
|||||||
export class BrowserContext extends BrowserContextBase {
|
export class BrowserContext extends BrowserContextBase {
|
||||||
#connection: Connection;
|
#connection: Connection;
|
||||||
#defaultViewport: Viewport | null;
|
#defaultViewport: Viewport | null;
|
||||||
|
#pages = new Map<string, Page>();
|
||||||
|
#onContextDestroyedBind = this.#onContextDestroyed.bind(this);
|
||||||
|
|
||||||
constructor(connection: Connection, options: BrowserContextOptions) {
|
constructor(connection: Connection, options: BrowserContextOptions) {
|
||||||
super();
|
super();
|
||||||
this.#connection = connection;
|
this.#connection = connection;
|
||||||
this.#defaultViewport = options.defaultViewport;
|
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> {
|
override async newPage(): Promise<PageBase> {
|
||||||
const {result} = await this.#connection.send('browsingContext.create', {
|
const {result} = await this.#connection.send('browsingContext.create', {
|
||||||
type: 'tab',
|
type: 'tab',
|
||||||
});
|
});
|
||||||
const context = this.#connection.context(result.context) as Context;
|
const page = await Page._create(this.#connection, result);
|
||||||
const page = new Page(context);
|
|
||||||
if (this.#defaultViewport) {
|
if (this.#defaultViewport) {
|
||||||
try {
|
try {
|
||||||
await page.setViewport(this.#defaultViewport);
|
await page.setViewport(this.#defaultViewport);
|
||||||
@ -52,8 +69,18 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
// No support for setViewport in Firefox.
|
// No support for setViewport in Firefox.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#pages.set(result.context, page);
|
||||||
|
|
||||||
return 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': {
|
'browsingContext.close': {
|
||||||
params: Bidi.BrowsingContext.CloseParameters;
|
params: Bidi.BrowsingContext.CloseParameters;
|
||||||
returnType: Bidi.BrowsingContext.CloseResult;
|
returnType: Bidi.Message.EmptyResult;
|
||||||
|
};
|
||||||
|
'browsingContext.getTree': {
|
||||||
|
params: Bidi.BrowsingContext.GetTreeParameters;
|
||||||
|
returnType: Bidi.BrowsingContext.GetTreeResult;
|
||||||
};
|
};
|
||||||
'browsingContext.navigate': {
|
'browsingContext.navigate': {
|
||||||
params: Bidi.BrowsingContext.NavigateParameters;
|
params: Bidi.BrowsingContext.NavigateParameters;
|
||||||
@ -73,11 +77,11 @@ interface Commands {
|
|||||||
returnType: Bidi.Session.StatusResult;
|
returnType: Bidi.Session.StatusResult;
|
||||||
};
|
};
|
||||||
'session.subscribe': {
|
'session.subscribe': {
|
||||||
params: Bidi.Session.SubscribeParameters;
|
params: Bidi.Session.SubscriptionRequest;
|
||||||
returnType: Bidi.Session.SubscribeResult;
|
returnType: Bidi.Session.SubscribeResult;
|
||||||
};
|
};
|
||||||
'session.unsubscribe': {
|
'session.unsubscribe': {
|
||||||
params: Bidi.Session.SubscribeParameters;
|
params: Bidi.Session.SubscriptionRequest;
|
||||||
returnType: Bidi.Session.UnsubscribeResult;
|
returnType: Bidi.Session.UnsubscribeResult;
|
||||||
};
|
};
|
||||||
'cdp.sendCommand': {
|
'cdp.sendCommand': {
|
||||||
@ -159,7 +163,6 @@ export class Connection extends EventEmitter {
|
|||||||
this.#callbacks.resolve(object.id, object);
|
this.#callbacks.resolve(object.id, object);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.#handleSpecialEvents(object);
|
|
||||||
this.#maybeEmitOnContext(object);
|
this.#maybeEmitOnContext(object);
|
||||||
this.emit(object.method, object.params);
|
this.emit(object.method, object.params);
|
||||||
}
|
}
|
||||||
@ -177,14 +180,12 @@ export class Connection extends EventEmitter {
|
|||||||
context?.emit(event.method, event.params);
|
context?.emit(event.method, event.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleSpecialEvents(event: Bidi.Message.EventMessage) {
|
registerContext(context: Context): void {
|
||||||
switch (event.method) {
|
this.#contexts.set(context.id, context);
|
||||||
case 'browsingContext.contextCreated':
|
}
|
||||||
this.#contexts.set(
|
|
||||||
event.params.context,
|
unregisterContext(context: Context): void {
|
||||||
new Context(this, event.params)
|
this.#contexts.delete(context.id);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#onClose(): void {
|
#onClose(): void {
|
||||||
|
@ -24,7 +24,6 @@ import {ProtocolMapping} from '../Connection.js';
|
|||||||
import {ProtocolError, TimeoutError} from '../Errors.js';
|
import {ProtocolError, TimeoutError} from '../Errors.js';
|
||||||
import {EventEmitter} from '../EventEmitter.js';
|
import {EventEmitter} from '../EventEmitter.js';
|
||||||
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
||||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
|
||||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||||
import {
|
import {
|
||||||
getSourcePuppeteerURLIfAvailable,
|
getSourcePuppeteerURLIfAvailable,
|
||||||
@ -36,6 +35,7 @@ import {
|
|||||||
|
|
||||||
import {Connection} from './Connection.js';
|
import {Connection} from './Connection.js';
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
|
import {FrameManager} from './FrameManager.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {BidiSerializer} from './Serializer.js';
|
import {BidiSerializer} from './Serializer.js';
|
||||||
import {createEvaluationError} from './utils.js';
|
import {createEvaluationError} from './utils.js';
|
||||||
@ -71,14 +71,28 @@ const lifeCycleToSubscribedEvent = new Map<PuppeteerLifeCycleEvent, string>([
|
|||||||
export class Context extends EventEmitter {
|
export class Context extends EventEmitter {
|
||||||
#connection: Connection;
|
#connection: Connection;
|
||||||
#url: string;
|
#url: string;
|
||||||
_contextId: string;
|
#id: string;
|
||||||
_timeoutSettings = new TimeoutSettings();
|
#parentId?: string | null;
|
||||||
|
_frameManager: FrameManager;
|
||||||
|
|
||||||
constructor(connection: Connection, result: Bidi.BrowsingContext.Info) {
|
constructor(
|
||||||
|
connection: Connection,
|
||||||
|
frameManager: FrameManager,
|
||||||
|
result: Bidi.BrowsingContext.Info
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.#connection = connection;
|
this.#connection = connection;
|
||||||
this._contextId = result.context;
|
this._frameManager = frameManager;
|
||||||
|
this.#id = result.context;
|
||||||
|
this.#parentId = result.parent;
|
||||||
this.#url = result.url;
|
this.#url = result.url;
|
||||||
|
|
||||||
|
this.on(
|
||||||
|
'browsingContext.fragmentNavigated',
|
||||||
|
(info: Bidi.BrowsingContext.NavigationInfo) => {
|
||||||
|
this.#url = info.url;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get connection(): Connection {
|
get connection(): Connection {
|
||||||
@ -86,7 +100,11 @@ export class Context extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get id(): string {
|
get id(): string {
|
||||||
return this._contextId;
|
return this.#id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parentId(): string | undefined | null {
|
||||||
|
return this.#parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateHandle<
|
async evaluateHandle<
|
||||||
@ -146,8 +164,8 @@ export class Context extends EventEmitter {
|
|||||||
: `${pageFunction}\n${sourceUrlComment}\n`;
|
: `${pageFunction}\n${sourceUrlComment}\n`;
|
||||||
|
|
||||||
responsePromise = this.#connection.send('script.evaluate', {
|
responsePromise = this.#connection.send('script.evaluate', {
|
||||||
expression: expression,
|
expression,
|
||||||
target: {context: this._contextId},
|
target: {context: this.#id},
|
||||||
resultOwnership,
|
resultOwnership,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
});
|
});
|
||||||
@ -163,7 +181,7 @@ export class Context extends EventEmitter {
|
|||||||
return BidiSerializer.serialize(arg, this);
|
return BidiSerializer.serialize(arg, this);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
target: {context: this._contextId},
|
target: {context: this.#id},
|
||||||
resultOwnership,
|
resultOwnership,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
});
|
});
|
||||||
@ -189,7 +207,7 @@ export class Context extends EventEmitter {
|
|||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
const {
|
const {
|
||||||
waitUntil = 'load',
|
waitUntil = 'load',
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._frameManager._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const readinessState = lifeCycleToReadinessState.get(
|
const readinessState = lifeCycleToReadinessState.get(
|
||||||
@ -229,7 +247,7 @@ export class Context extends EventEmitter {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
waitUntil = 'load',
|
waitUntil = 'load',
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._frameManager._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const waitUntilCommand = lifeCycleToSubscribedEvent.get(
|
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(
|
async sendCDPCommand(
|
||||||
method: keyof ProtocolMapping.Commands,
|
method: keyof ProtocolMapping.Commands,
|
||||||
params: object = {}
|
params: object = {}
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
const session = await this.#connection.send('cdp.getSession', {
|
const session = await this.#connection.send('cdp.getSession', {
|
||||||
context: this._contextId,
|
context: this.id,
|
||||||
});
|
});
|
||||||
// TODO: remove any once chromium-bidi types are updated.
|
// TODO: remove any once chromium-bidi types are updated.
|
||||||
const sessionId = (session.result as any).cdpSession;
|
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';
|
} from '../../api/Page.js';
|
||||||
import {isErrorLike} from '../../util/ErrorLike.js';
|
import {isErrorLike} from '../../util/ErrorLike.js';
|
||||||
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.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 {PDFOptions} from '../PDFOptions.js';
|
||||||
import {Viewport} from '../PuppeteerViewport.js';
|
import {Viewport} from '../PuppeteerViewport.js';
|
||||||
|
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||||
import {
|
import {
|
||||||
debugError,
|
debugError,
|
||||||
@ -37,31 +39,77 @@ import {
|
|||||||
withSourcePuppeteerURLIfNone,
|
withSourcePuppeteerURLIfNone,
|
||||||
} from '../util.js';
|
} from '../util.js';
|
||||||
|
|
||||||
|
import {Connection} from './Connection.js';
|
||||||
import {Context, getBidiHandle} from './Context.js';
|
import {Context, getBidiHandle} from './Context.js';
|
||||||
|
import {Frame} from './Frame.js';
|
||||||
|
import {FrameManager} from './FrameManager.js';
|
||||||
import {BidiSerializer} from './Serializer.js';
|
import {BidiSerializer} from './Serializer.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class Page extends PageBase {
|
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>>([
|
#subscribedEvents = new Map<string, Handler<any>>([
|
||||||
['log.entryAdded', this.#onLogEntryAdded.bind(this)],
|
['log.entryAdded', this.#onLogEntryAdded.bind(this)],
|
||||||
['browsingContext.load', this.#onLoad.bind(this)],
|
['browsingContext.load', this.#onLoad.bind(this)],
|
||||||
['browsingContext.domContentLoaded', this.#onDOMLoad.bind(this)],
|
['browsingContext.domContentLoaded', this.#onDOMLoad.bind(this)],
|
||||||
]) as Map<Bidi.Session.SubscribeParametersEvent, Handler>;
|
]) as Map<Bidi.Session.SubscriptionRequestEvent, Handler>;
|
||||||
#viewport: Viewport | null = null;
|
#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();
|
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', {
|
.send('session.subscribe', {
|
||||||
events: [
|
events: [...page.#subscribedEvents.keys()],
|
||||||
...this.#subscribedEvents.keys(),
|
contexts: [info.context],
|
||||||
] as Bidi.Session.SubscribeParameters['events'],
|
|
||||||
contexts: [this.#context.id],
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (isErrorLike(error) && !error.message.includes('Target closed')) {
|
if (isErrorLike(error) && !error.message.includes('Target closed')) {
|
||||||
@ -69,15 +117,25 @@ export class Page extends PageBase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
return page;
|
||||||
this.#context.on(event, subscriber);
|
}
|
||||||
}
|
|
||||||
|
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 {
|
#onLogEntryAdded(event: Bidi.Log.LogEntry): void {
|
||||||
if (isConsoleLogEntry(event)) {
|
if (isConsoleLogEntry(event)) {
|
||||||
const args = event.args.map(arg => {
|
const args = event.args.map(arg => {
|
||||||
return getBidiHandle(this.#context, arg);
|
return getBidiHandle(this.context(), arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = args
|
const text = args
|
||||||
@ -134,18 +192,30 @@ export class Page extends PageBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async close(): Promise<void> {
|
override async close(): Promise<void> {
|
||||||
await this.#context.connection.send('session.unsubscribe', {
|
if (this.#closed) {
|
||||||
events: [...this.#subscribedEvents.keys()],
|
return;
|
||||||
contexts: [this.#context.id],
|
}
|
||||||
});
|
this.#closed = true;
|
||||||
|
this.removeAllListeners();
|
||||||
await this.#context.connection.send('browsingContext.close', {
|
this.#frameManager.dispose();
|
||||||
context: this.#context.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
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<
|
override async evaluateHandle<
|
||||||
@ -159,7 +229,7 @@ export class Page extends PageBase {
|
|||||||
this.evaluateHandle.name,
|
this.evaluateHandle.name,
|
||||||
pageFunction
|
pageFunction
|
||||||
);
|
);
|
||||||
return this.#context.evaluateHandle(pageFunction, ...args);
|
return this.mainFrame().evaluateHandle(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async evaluate<
|
override async evaluate<
|
||||||
@ -173,7 +243,7 @@ export class Page extends PageBase {
|
|||||||
this.evaluate.name,
|
this.evaluate.name,
|
||||||
pageFunction
|
pageFunction
|
||||||
);
|
);
|
||||||
return this.#context.evaluate(pageFunction, ...args);
|
return this.mainFrame().evaluate(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async goto(
|
override async goto(
|
||||||
@ -183,26 +253,26 @@ export class Page extends PageBase {
|
|||||||
referrerPolicy?: string | undefined;
|
referrerPolicy?: string | undefined;
|
||||||
}
|
}
|
||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
return this.#context.goto(url, options);
|
return this.mainFrame().goto(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
override url(): string {
|
override url(): string {
|
||||||
return this.#context.url();
|
return this.mainFrame().url();
|
||||||
}
|
}
|
||||||
|
|
||||||
override setDefaultNavigationTimeout(timeout: number): void {
|
override setDefaultNavigationTimeout(timeout: number): void {
|
||||||
this.#context._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
override setDefaultTimeout(timeout: number): void {
|
override setDefaultTimeout(timeout: number): void {
|
||||||
this.#context._timeoutSettings.setDefaultTimeout(timeout);
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async setContent(
|
override async setContent(
|
||||||
html: string,
|
html: string,
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#context.setContent(html, options);
|
await this.mainFrame().setContent(html, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async content(): Promise<string> {
|
override async content(): Promise<string> {
|
||||||
@ -226,7 +296,7 @@ export class Page extends PageBase {
|
|||||||
const deviceScaleFactor = 1;
|
const deviceScaleFactor = 1;
|
||||||
const screenOrientation = {angle: 0, type: 'portraitPrimary'};
|
const screenOrientation = {angle: 0, type: 'portraitPrimary'};
|
||||||
|
|
||||||
await this.#context.sendCDPCommand('Emulation.setDeviceMetricsOverride', {
|
await this.context().sendCDPCommand('Emulation.setDeviceMetricsOverride', {
|
||||||
mobile,
|
mobile,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -255,8 +325,8 @@ export class Page extends PageBase {
|
|||||||
timeout,
|
timeout,
|
||||||
} = this._getPDFOptions(options, 'cm');
|
} = this._getPDFOptions(options, 'cm');
|
||||||
const {result} = await waitWithTimeout(
|
const {result} = await waitWithTimeout(
|
||||||
this.#context.connection.send('browsingContext.print', {
|
this.#connection.send('browsingContext.print', {
|
||||||
context: this.#context._contextId,
|
context: this.context().id,
|
||||||
background,
|
background,
|
||||||
margin,
|
margin,
|
||||||
orientation: landscape ? 'landscape' : 'portrait',
|
orientation: landscape ? 'landscape' : 'portrait',
|
||||||
@ -310,10 +380,10 @@ export class Page extends PageBase {
|
|||||||
throw new Error('BiDi only supports "encoding" and "path" options');
|
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',
|
'browsingContext.captureScreenshot',
|
||||||
{
|
{
|
||||||
context: this.#context._contextId,
|
context: this.context().id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class UnserializableError extends Error {}
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiSerializer {
|
export class BidiSerializer {
|
||||||
static serializeNumber(arg: number): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
static serializeNumber(arg: number): Bidi.CommonDataTypes.LocalValue {
|
||||||
let value: Bidi.CommonDataTypes.SpecialNumber | number;
|
let value: Bidi.CommonDataTypes.SpecialNumber | number;
|
||||||
if (Object.is(arg, -0)) {
|
if (Object.is(arg, -0)) {
|
||||||
value = '-0';
|
value = '-0';
|
||||||
@ -50,9 +50,7 @@ export class BidiSerializer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializeObject(
|
static serializeObject(arg: object | null): Bidi.CommonDataTypes.LocalValue {
|
||||||
arg: object | null
|
|
||||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
|
||||||
if (arg === null) {
|
if (arg === null) {
|
||||||
return {
|
return {
|
||||||
type: 'null',
|
type: 'null',
|
||||||
@ -111,9 +109,7 @@ export class BidiSerializer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializeRemoveValue(
|
static serializeRemoveValue(arg: unknown): Bidi.CommonDataTypes.LocalValue {
|
||||||
arg: unknown
|
|
||||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
|
||||||
switch (typeof arg) {
|
switch (typeof arg) {
|
||||||
case 'symbol':
|
case 'symbol':
|
||||||
case 'function':
|
case 'function':
|
||||||
@ -148,7 +144,7 @@ export class BidiSerializer {
|
|||||||
static serialize(
|
static serialize(
|
||||||
arg: unknown,
|
arg: unknown,
|
||||||
context: Context
|
context: Context
|
||||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
): Bidi.CommonDataTypes.LocalValue | Bidi.CommonDataTypes.RemoteValue {
|
||||||
// TODO: See use case of LazyArgs
|
// TODO: See use case of LazyArgs
|
||||||
const objectHandle =
|
const objectHandle =
|
||||||
arg && (arg instanceof JSHandle || arg instanceof ElementHandle)
|
arg && (arg instanceof JSHandle || arg instanceof ElementHandle)
|
||||||
|
@ -38,7 +38,7 @@ export async function releaseReference(
|
|||||||
}
|
}
|
||||||
await client.connection
|
await client.connection
|
||||||
.send('script.disown', {
|
.send('script.disown', {
|
||||||
target: {context: client._contextId},
|
target: {context: client.id},
|
||||||
handles: [remoteReference.handle],
|
handles: [remoteReference.handle],
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
|
@ -38,7 +38,6 @@ export * from './ExecutionContext.js';
|
|||||||
export * from './fetch.js';
|
export * from './fetch.js';
|
||||||
export * from './FileChooser.js';
|
export * from './FileChooser.js';
|
||||||
export * from './FirefoxTargetManager.js';
|
export * from './FirefoxTargetManager.js';
|
||||||
export * from './Frame.js';
|
|
||||||
export * from './FrameManager.js';
|
export * from './FrameManager.js';
|
||||||
export * from './FrameTree.js';
|
export * from './FrameTree.js';
|
||||||
export * from './Input.js';
|
export * from './Input.js';
|
||||||
|
@ -71,6 +71,12 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[page.spec] Page Page.Events.Load *",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["PASS"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[page.spec] Page Page.Events.PageError *",
|
"testIdPattern": "[page.spec] Page Page.Events.PageError *",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -143,12 +149,6 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL"]
|
"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",
|
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have correct execution contexts",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -245,6 +245,48 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["SKIP"]
|
"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] *",
|
"testIdPattern": "[headful.spec] *",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -2252,7 +2294,13 @@
|
|||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
||||||
"platforms": ["linux"],
|
"platforms": ["linux"],
|
||||||
"parameters": ["cdp", "chrome", "headless", "webDriverBiDi"],
|
"parameters": ["chrome", "headless"],
|
||||||
"expectations": ["PASS", "TIMEOUT"]
|
"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 expect from 'expect';
|
||||||
|
import {Frame} from 'puppeteer-core/internal/api/Frame.js';
|
||||||
import {CDPSession} from 'puppeteer-core/internal/common/Connection.js';
|
import {CDPSession} from 'puppeteer-core/internal/common/Connection.js';
|
||||||
import {Frame} from 'puppeteer-core/internal/common/Frame.js';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getTestState,
|
getTestState,
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
import {attachFrame, detachFrame, waitEvent} from './utils.js';
|
import {attachFrame, detachFrame, isFavicon, waitEvent} from './utils.js';
|
||||||
|
|
||||||
describe('Page', function () {
|
describe('Page', function () {
|
||||||
setupTestBrowserHooks();
|
setupTestBrowserHooks();
|
||||||
@ -135,7 +135,7 @@ describe('Page', function () {
|
|||||||
const handler = sinon.spy();
|
const handler = sinon.spy();
|
||||||
const onResponse = (response: {url: () => string}) => {
|
const onResponse = (response: {url: () => string}) => {
|
||||||
// Ignore default favicon requests.
|
// Ignore default favicon requests.
|
||||||
if (!response.url().endsWith('favicon.ico')) {
|
if (!isFavicon(response)) {
|
||||||
handler();
|
handler();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -158,7 +158,7 @@ describe('Page', function () {
|
|||||||
const handler = sinon.spy();
|
const handler = sinon.spy();
|
||||||
const onResponse = (response: {url: () => string}) => {
|
const onResponse = (response: {url: () => string}) => {
|
||||||
// Ignore default favicon requests.
|
// Ignore default favicon requests.
|
||||||
if (!response.url().endsWith('favicon.ico')) {
|
if (!isFavicon(response)) {
|
||||||
handler();
|
handler();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
|
import {Frame} from 'puppeteer-core/internal/api/Frame.js';
|
||||||
import {Page} from 'puppeteer-core/internal/api/Page.js';
|
import {Page} from 'puppeteer-core/internal/api/Page.js';
|
||||||
import {EventEmitter} from 'puppeteer-core/internal/common/EventEmitter.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';
|
import {compare} from './golden-utils.js';
|
||||||
|
|
||||||
|
@ -119,6 +119,7 @@ async function main() {
|
|||||||
? {
|
? {
|
||||||
DEBUG: 'puppeteer:*',
|
DEBUG: 'puppeteer:*',
|
||||||
EXTRA_LAUNCH_OPTIONS: JSON.stringify({
|
EXTRA_LAUNCH_OPTIONS: JSON.stringify({
|
||||||
|
dumpio: true,
|
||||||
extraPrefsFirefox: {
|
extraPrefsFirefox: {
|
||||||
'remote.log.level': 'Trace',
|
'remote.log.level': 'Trace',
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user