mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: use strict typescript (#8401)
This commit is contained in:
parent
f67bfb7a6c
commit
b4e751f29c
@ -49,7 +49,7 @@
|
|||||||
"generate-api-docs-for-testing": "commonmark docs/api.md > docs/api.html",
|
"generate-api-docs-for-testing": "commonmark docs/api.md > docs/api.html",
|
||||||
"clean-lib": "rimraf lib",
|
"clean-lib": "rimraf lib",
|
||||||
"build": "npm run tsc && npm run generate-d-ts && npm run generate-esm-package-json",
|
"build": "npm run tsc && npm run generate-d-ts && npm run generate-esm-package-json",
|
||||||
"tsc": "npm run clean-lib && tsc --version && (npm run tsc-cjs & npm run tsc-esm) && (npm run tsc-compat-cjs & npm run tsc-compat-esm)",
|
"tsc": "npm run clean-lib && tsc --version && (npm run tsc-cjs && npm run tsc-esm) && (npm run tsc-compat-cjs && npm run tsc-compat-esm)",
|
||||||
"tsc-cjs": "tsc -b src/tsconfig.cjs.json",
|
"tsc-cjs": "tsc -b src/tsconfig.cjs.json",
|
||||||
"tsc-esm": "tsc -b src/tsconfig.esm.json",
|
"tsc-esm": "tsc -b src/tsconfig.esm.json",
|
||||||
"tsc-compat-cjs": "tsc -b compat/cjs/tsconfig.json",
|
"tsc-compat-cjs": "tsc -b compat/cjs/tsconfig.json",
|
||||||
@ -107,6 +107,7 @@
|
|||||||
"@types/rimraf": "3.0.2",
|
"@types/rimraf": "3.0.2",
|
||||||
"@types/sinon": "10.0.11",
|
"@types/sinon": "10.0.11",
|
||||||
"@types/tar-fs": "2.0.1",
|
"@types/tar-fs": "2.0.1",
|
||||||
|
"@types/unbzip2-stream": "1.4.0",
|
||||||
"@types/ws": "8.5.3",
|
"@types/ws": "8.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.23.0",
|
"@typescript-eslint/eslint-plugin": "5.23.0",
|
||||||
"@typescript-eslint/parser": "5.22.0",
|
"@typescript-eslint/parser": "5.22.0",
|
||||||
|
@ -66,7 +66,7 @@ const output = execSync(command, {
|
|||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
});
|
});
|
||||||
|
|
||||||
const bestRevisionFromNpm = output.split(' ')[1].replace(/'|\n/g, '');
|
const bestRevisionFromNpm = output.split(' ')[1]!.replace(/'|\n/g, '');
|
||||||
|
|
||||||
if (currentProtocolPackageInstalledVersion !== bestRevisionFromNpm) {
|
if (currentProtocolPackageInstalledVersion !== bestRevisionFromNpm) {
|
||||||
console.log(`ERROR: bad devtools-protocol revision detected:
|
console.log(`ERROR: bad devtools-protocol revision detected:
|
||||||
|
@ -21,7 +21,7 @@ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|||||||
const invalidDeps = new Map<string, string>();
|
const invalidDeps = new Map<string, string>();
|
||||||
|
|
||||||
for (const [depKey, depValue] of Object.entries(allDeps)) {
|
for (const [depKey, depValue] of Object.entries(allDeps)) {
|
||||||
if (/[0-9]/.test(depValue[0])) {
|
if (/[0-9]/.test(depValue[0]!)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ const EXPECTED_ERRORS = new Map<string, string[]>([
|
|||||||
"bad.ts(6,35): error TS2551: Property 'launh' does not exist on type",
|
"bad.ts(6,35): error TS2551: Property 'launh' does not exist on type",
|
||||||
"bad.ts(8,29): error TS2551: Property 'devics' does not exist on type",
|
"bad.ts(8,29): error TS2551: Property 'devics' does not exist on type",
|
||||||
'bad.ts(12,39): error TS2554: Expected 0 arguments, but got 1.',
|
'bad.ts(12,39): error TS2554: Expected 0 arguments, but got 1.',
|
||||||
"bad.ts(20,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn<HTMLAnchorElement>",
|
"bad.ts(20,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn<HTMLAnchorElement, any, any>'.",
|
||||||
"bad.ts(20,34): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
"bad.ts(20,34): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
||||||
"bad.ts(24,45): error TS2344: Type '(x: number) => string' does not satisfy the constraint 'EvaluateFn<HTMLAnchorElement>'.",
|
"bad.ts(24,45): error TS2344: Type '(x: number) => string' does not satisfy the constraint 'EvaluateFn<HTMLAnchorElement, any, any>'.",
|
||||||
"bad.ts(27,34): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
"bad.ts(27,34): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -39,7 +39,7 @@ const EXPECTED_ERRORS = new Map<string, string[]>([
|
|||||||
"bad.js(7,29): error TS2551: Property 'devics' does not exist on type",
|
"bad.js(7,29): error TS2551: Property 'devics' does not exist on type",
|
||||||
'bad.js(11,39): error TS2554: Expected 0 arguments, but got 1.',
|
'bad.js(11,39): error TS2554: Expected 0 arguments, but got 1.',
|
||||||
"bad.js(15,9): error TS2322: Type 'ElementHandle<HTMLElement> | null' is not assignable to type 'ElementHandle<HTMLElement>'",
|
"bad.js(15,9): error TS2322: Type 'ElementHandle<HTMLElement> | null' is not assignable to type 'ElementHandle<HTMLElement>'",
|
||||||
"bad.js(22,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn<HTMLElement>'.",
|
"bad.js(22,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn<HTMLElement, any, any>'.",
|
||||||
"bad.js(22,26): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
"bad.js(22,26): error TS2339: Property 'innerText' does not exist on type 'number'.",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -73,7 +73,7 @@ const EXPECTED_ERRORS = new Map<string, string[]>([
|
|||||||
]);
|
]);
|
||||||
const PROJECT_FOLDERS = [...EXPECTED_ERRORS.keys()];
|
const PROJECT_FOLDERS = [...EXPECTED_ERRORS.keys()];
|
||||||
|
|
||||||
if (!process.env.CI) {
|
if (!process.env['CI']) {
|
||||||
console.log(`IMPORTANT: this script assumes you have compiled Puppeteer
|
console.log(`IMPORTANT: this script assumes you have compiled Puppeteer
|
||||||
and its types file before running. Make sure you have run:
|
and its types file before running. Make sure you have run:
|
||||||
=> npm run tsc && npm run generate-d-ts
|
=> npm run tsc && npm run generate-d-ts
|
||||||
@ -107,7 +107,7 @@ function packPuppeteer() {
|
|||||||
const tar = packPuppeteer();
|
const tar = packPuppeteer();
|
||||||
const tarPath = path.join(process.cwd(), tar);
|
const tarPath = path.join(process.cwd(), tar);
|
||||||
|
|
||||||
function compileAndCatchErrors(projectLocation) {
|
function compileAndCatchErrors(projectLocation: string) {
|
||||||
const { status, stdout, stderr } = spawnSync('npm', ['run', 'compile'], {
|
const { status, stdout, stderr } = spawnSync('npm', ['run', 'compile'], {
|
||||||
cwd: projectLocation,
|
cwd: projectLocation,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
@ -159,7 +159,7 @@ function testProject(folder: string) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status > 0) {
|
if (status) {
|
||||||
console.error(
|
console.error(
|
||||||
'Installing the tar file unexpectedly failed',
|
'Installing the tar file unexpectedly failed',
|
||||||
stdout,
|
stdout,
|
||||||
|
@ -180,10 +180,10 @@ export class Accessibility {
|
|||||||
*/
|
*/
|
||||||
public async snapshot(
|
public async snapshot(
|
||||||
options: SnapshotOptions = {}
|
options: SnapshotOptions = {}
|
||||||
): Promise<SerializedAXNode> {
|
): Promise<SerializedAXNode | null> {
|
||||||
const { interestingOnly = true, root = null } = options;
|
const { interestingOnly = true, root = null } = options;
|
||||||
const { nodes } = await this._client.send('Accessibility.getFullAXTree');
|
const { nodes } = await this._client.send('Accessibility.getFullAXTree');
|
||||||
let backendNodeId = null;
|
let backendNodeId: number | undefined;
|
||||||
if (root) {
|
if (root) {
|
||||||
const { node } = await this._client.send('DOM.describeNode', {
|
const { node } = await this._client.send('DOM.describeNode', {
|
||||||
objectId: root._remoteObject.objectId,
|
objectId: root._remoteObject.objectId,
|
||||||
@ -191,19 +191,19 @@ export class Accessibility {
|
|||||||
backendNodeId = node.backendNodeId;
|
backendNodeId = node.backendNodeId;
|
||||||
}
|
}
|
||||||
const defaultRoot = AXNode.createTree(nodes);
|
const defaultRoot = AXNode.createTree(nodes);
|
||||||
let needle = defaultRoot;
|
let needle: AXNode | null = defaultRoot;
|
||||||
if (backendNodeId) {
|
if (backendNodeId) {
|
||||||
needle = defaultRoot.find(
|
needle = defaultRoot.find(
|
||||||
(node) => node.payload.backendDOMNodeId === backendNodeId
|
(node) => node.payload.backendDOMNodeId === backendNodeId
|
||||||
);
|
);
|
||||||
if (!needle) return null;
|
if (!needle) return null;
|
||||||
}
|
}
|
||||||
if (!interestingOnly) return this.serializeTree(needle)[0];
|
if (!interestingOnly) return this.serializeTree(needle)[0] ?? null;
|
||||||
|
|
||||||
const interestingNodes = new Set<AXNode>();
|
const interestingNodes = new Set<AXNode>();
|
||||||
this.collectInterestingNodes(interestingNodes, defaultRoot, false);
|
this.collectInterestingNodes(interestingNodes, defaultRoot, false);
|
||||||
if (!interestingNodes.has(needle)) return null;
|
if (!interestingNodes.has(needle)) return null;
|
||||||
return this.serializeTree(needle, interestingNodes)[0];
|
return this.serializeTree(needle, interestingNodes)[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private serializeTree(
|
private serializeTree(
|
||||||
@ -496,7 +496,7 @@ class AXNode {
|
|||||||
nodeById.set(payload.nodeId, new AXNode(payload));
|
nodeById.set(payload.nodeId, new AXNode(payload));
|
||||||
for (const node of nodeById.values()) {
|
for (const node of nodeById.values()) {
|
||||||
for (const childId of node.payload.childIds || [])
|
for (const childId of node.payload.childIds || [])
|
||||||
node.children.push(nodeById.get(childId));
|
node.children.push(nodeById.get(childId)!);
|
||||||
}
|
}
|
||||||
return nodeById.values().next().value;
|
return nodeById.values().next().value;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { ElementHandle, JSHandle } from './JSHandle.js';
|
|||||||
import { Protocol } from 'devtools-protocol';
|
import { Protocol } from 'devtools-protocol';
|
||||||
import { CDPSession } from './Connection.js';
|
import { CDPSession } from './Connection.js';
|
||||||
import { DOMWorld, PageBinding, WaitForSelectorOptions } from './DOMWorld.js';
|
import { DOMWorld, PageBinding, WaitForSelectorOptions } from './DOMWorld.js';
|
||||||
|
import { assert } from './assert.js';
|
||||||
|
|
||||||
async function queryAXTree(
|
async function queryAXTree(
|
||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
@ -32,7 +33,8 @@ async function queryAXTree(
|
|||||||
role,
|
role,
|
||||||
});
|
});
|
||||||
const filteredNodes: Protocol.Accessibility.AXNode[] = nodes.filter(
|
const filteredNodes: Protocol.Accessibility.AXNode[] = nodes.filter(
|
||||||
(node: Protocol.Accessibility.AXNode) => node.role.value !== 'StaticText'
|
(node: Protocol.Accessibility.AXNode) =>
|
||||||
|
!node.role || node.role.value !== 'StaticText'
|
||||||
);
|
);
|
||||||
return filteredNodes;
|
return filteredNodes;
|
||||||
}
|
}
|
||||||
@ -43,6 +45,13 @@ const knownAttributes = new Set(['name', 'role']);
|
|||||||
const attributeRegexp =
|
const attributeRegexp =
|
||||||
/\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/g;
|
/\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/g;
|
||||||
|
|
||||||
|
type ARIAQueryOption = { name?: string; role?: string };
|
||||||
|
function isKnownAttribute(
|
||||||
|
attribute: string
|
||||||
|
): attribute is keyof ARIAQueryOption {
|
||||||
|
return knownAttributes.has(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The selectors consist of an accessible name to query for and optionally
|
* The selectors consist of an accessible name to query for and optionally
|
||||||
* further aria attributes on the form `[<attribute>=<value>]`.
|
* further aria attributes on the form `[<attribute>=<value>]`.
|
||||||
@ -53,15 +62,16 @@ const attributeRegexp =
|
|||||||
* - 'label' queries for elements with name 'label' and any role.
|
* - 'label' queries for elements with name 'label' and any role.
|
||||||
* - '[name=""][role="button"]' queries for elements with no name and role 'button'.
|
* - '[name=""][role="button"]' queries for elements with no name and role 'button'.
|
||||||
*/
|
*/
|
||||||
type ariaQueryOption = { name?: string; role?: string };
|
function parseAriaSelector(selector: string): ARIAQueryOption {
|
||||||
function parseAriaSelector(selector: string): ariaQueryOption {
|
const queryOptions: ARIAQueryOption = {};
|
||||||
const queryOptions: ariaQueryOption = {};
|
|
||||||
const defaultName = selector.replace(
|
const defaultName = selector.replace(
|
||||||
attributeRegexp,
|
attributeRegexp,
|
||||||
(_, attribute: string, quote: string, value: string) => {
|
(_, attribute: string, _quote: string, value: string) => {
|
||||||
attribute = attribute.trim();
|
attribute = attribute.trim();
|
||||||
if (!knownAttributes.has(attribute))
|
assert(
|
||||||
throw new Error(`Unknown aria attribute "${attribute}" in selector`);
|
isKnownAttribute(attribute),
|
||||||
|
`Unknown aria attribute "${attribute}" in selector`
|
||||||
|
);
|
||||||
queryOptions[attribute] = normalizeValue(value);
|
queryOptions[attribute] = normalizeValue(value);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -78,7 +88,7 @@ const queryOne = async (
|
|||||||
const exeCtx = element.executionContext();
|
const exeCtx = element.executionContext();
|
||||||
const { name, role } = parseAriaSelector(selector);
|
const { name, role } = parseAriaSelector(selector);
|
||||||
const res = await queryAXTree(exeCtx._client, element, name, role);
|
const res = await queryAXTree(exeCtx._client, element, name, role);
|
||||||
if (res.length < 1) {
|
if (!res[0] || !res[0].backendDOMNodeId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return exeCtx._adoptBackendNodeId(res[0].backendDOMNodeId);
|
return exeCtx._adoptBackendNodeId(res[0].backendDOMNodeId);
|
||||||
@ -88,7 +98,7 @@ const waitFor = async (
|
|||||||
domWorld: DOMWorld,
|
domWorld: DOMWorld,
|
||||||
selector: string,
|
selector: string,
|
||||||
options: WaitForSelectorOptions
|
options: WaitForSelectorOptions
|
||||||
): Promise<ElementHandle<Element>> => {
|
): Promise<ElementHandle<Element> | null> => {
|
||||||
const binding: PageBinding = {
|
const binding: PageBinding = {
|
||||||
name: 'ariaQuerySelector',
|
name: 'ariaQuerySelector',
|
||||||
pptrFunction: async (selector: string) => {
|
pptrFunction: async (selector: string) => {
|
||||||
@ -98,7 +108,10 @@ const waitFor = async (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
return domWorld.waitForSelectorInPage(
|
return domWorld.waitForSelectorInPage(
|
||||||
(_: Element, selector: string) => globalThis.ariaQuerySelector(selector),
|
(_: Element, selector: string) =>
|
||||||
|
(
|
||||||
|
globalThis as unknown as { ariaQuerySelector(selector: string): void }
|
||||||
|
).ariaQuerySelector(selector),
|
||||||
selector,
|
selector,
|
||||||
options,
|
options,
|
||||||
binding
|
binding
|
||||||
|
@ -246,7 +246,7 @@ export class Browser extends EventEmitter {
|
|||||||
private _connection: Connection;
|
private _connection: Connection;
|
||||||
private _closeCallback: BrowserCloseCallback;
|
private _closeCallback: BrowserCloseCallback;
|
||||||
private _targetFilterCallback: TargetFilterCallback;
|
private _targetFilterCallback: TargetFilterCallback;
|
||||||
private _isPageTargetCallback: IsPageTargetCallback;
|
private _isPageTargetCallback!: IsPageTargetCallback;
|
||||||
private _defaultContext: BrowserContext;
|
private _defaultContext: BrowserContext;
|
||||||
private _contexts: Map<string, BrowserContext>;
|
private _contexts: Map<string, BrowserContext>;
|
||||||
private _screenshotTaskQueue: TaskQueue;
|
private _screenshotTaskQueue: TaskQueue;
|
||||||
@ -572,33 +572,24 @@ export class Browser extends EventEmitter {
|
|||||||
): Promise<Target> {
|
): Promise<Target> {
|
||||||
const { timeout = 30000 } = options;
|
const { timeout = 30000 } = options;
|
||||||
let resolve: (value: Target | PromiseLike<Target>) => void;
|
let resolve: (value: Target | PromiseLike<Target>) => void;
|
||||||
|
let isResolved = false;
|
||||||
const targetPromise = new Promise<Target>((x) => (resolve = x));
|
const targetPromise = new Promise<Target>((x) => (resolve = x));
|
||||||
this.on(BrowserEmittedEvents.TargetCreated, check);
|
this.on(BrowserEmittedEvents.TargetCreated, check);
|
||||||
this.on(BrowserEmittedEvents.TargetChanged, check);
|
this.on(BrowserEmittedEvents.TargetChanged, check);
|
||||||
try {
|
try {
|
||||||
if (!timeout) return await targetPromise;
|
if (!timeout) return await targetPromise;
|
||||||
return await helper.waitWithTimeout<Target>(
|
this.targets().forEach(check);
|
||||||
Promise.race([
|
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
|
||||||
targetPromise,
|
|
||||||
(async () => {
|
|
||||||
for (const target of this.targets()) {
|
|
||||||
if (await predicate(target)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await targetPromise;
|
|
||||||
})(),
|
|
||||||
]),
|
|
||||||
'target',
|
|
||||||
timeout
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.removeListener(BrowserEmittedEvents.TargetCreated, check);
|
this.off(BrowserEmittedEvents.TargetCreated, check);
|
||||||
this.removeListener(BrowserEmittedEvents.TargetChanged, check);
|
this.off(BrowserEmittedEvents.TargetChanged, check);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function check(target: Target): Promise<void> {
|
async function check(target: Target): Promise<void> {
|
||||||
if (await predicate(target)) resolve(target);
|
if ((await predicate(target)) && !isResolved) {
|
||||||
|
isResolved = true;
|
||||||
|
resolve(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ export const connectToBrowser = async (
|
|||||||
'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect'
|
'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect'
|
||||||
);
|
);
|
||||||
|
|
||||||
let connection = null;
|
let connection!: Connection;
|
||||||
if (transport) {
|
if (transport) {
|
||||||
connection = new Connection('', transport, slowMo);
|
connection = new Connection('', transport, slowMo);
|
||||||
} else if (browserWSEndpoint) {
|
} else if (browserWSEndpoint) {
|
||||||
@ -117,7 +117,7 @@ export const connectToBrowser = async (
|
|||||||
browserContextIds,
|
browserContextIds,
|
||||||
ignoreHTTPSErrors,
|
ignoreHTTPSErrors,
|
||||||
defaultViewport,
|
defaultViewport,
|
||||||
null,
|
undefined,
|
||||||
() => connection.send('Browser.close').catch(debugError),
|
() => connection.send('Browser.close').catch(debugError),
|
||||||
targetFilter,
|
targetFilter,
|
||||||
isPageTarget
|
isPageTarget
|
||||||
@ -138,9 +138,11 @@ async function getWSEndpoint(browserURL: string): Promise<string> {
|
|||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
return data.webSocketDebuggerUrl;
|
return data.webSocketDebuggerUrl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message =
|
if (error instanceof Error) {
|
||||||
`Failed to fetch browser webSocket URL from ${endpointURL}: ` +
|
error.message =
|
||||||
error.message;
|
`Failed to fetch browser webSocket URL from ${endpointURL}: ` +
|
||||||
|
error.message;
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,6 @@ export class BrowserWebSocketTransport implements ConnectionTransport {
|
|||||||
});
|
});
|
||||||
// Silently ignore all errors - we don't know what to do with them.
|
// Silently ignore all errors - we don't know what to do with them.
|
||||||
this._ws.addEventListener('error', () => {});
|
this._ws.addEventListener('error', () => {});
|
||||||
this.onmessage = null;
|
|
||||||
this.onclose = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message: string): void {
|
send(message: string): void {
|
||||||
|
@ -111,7 +111,7 @@ export class ConsoleMessage {
|
|||||||
* @returns The location of the console message.
|
* @returns The location of the console message.
|
||||||
*/
|
*/
|
||||||
location(): ConsoleMessageLocation {
|
location(): ConsoleMessageLocation {
|
||||||
return this._stackTraceLocations.length ? this._stackTraceLocations[0] : {};
|
return this._stackTraceLocations[0] ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -395,10 +395,12 @@ export class CSSCoverage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const coverage = [];
|
const coverage: CoverageEntry[] = [];
|
||||||
for (const styleSheetId of this._stylesheetURLs.keys()) {
|
for (const styleSheetId of this._stylesheetURLs.keys()) {
|
||||||
const url = this._stylesheetURLs.get(styleSheetId);
|
const url = this._stylesheetURLs.get(styleSheetId);
|
||||||
|
assert(url);
|
||||||
const text = this._stylesheetSources.get(styleSheetId);
|
const text = this._stylesheetSources.get(styleSheetId);
|
||||||
|
assert(text);
|
||||||
const ranges = convertToDisjointRanges(
|
const ranges = convertToDisjointRanges(
|
||||||
styleSheetIdToCoverage.get(styleSheetId) || []
|
styleSheetIdToCoverage.get(styleSheetId) || []
|
||||||
);
|
);
|
||||||
@ -432,16 +434,19 @@ function convertToDisjointRanges(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hitCountStack = [];
|
const hitCountStack = [];
|
||||||
const results = [];
|
const results: Array<{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}> = [];
|
||||||
let lastOffset = 0;
|
let lastOffset = 0;
|
||||||
// Run scanning line to intersect all ranges.
|
// Run scanning line to intersect all ranges.
|
||||||
for (const point of points) {
|
for (const point of points) {
|
||||||
if (
|
if (
|
||||||
hitCountStack.length &&
|
hitCountStack.length &&
|
||||||
lastOffset < point.offset &&
|
lastOffset < point.offset &&
|
||||||
hitCountStack[hitCountStack.length - 1] > 0
|
hitCountStack[hitCountStack.length - 1]! > 0
|
||||||
) {
|
) {
|
||||||
const lastResult = results.length ? results[results.length - 1] : null;
|
const lastResult = results[results.length - 1];
|
||||||
if (lastResult && lastResult.end === lastOffset)
|
if (lastResult && lastResult.end === lastOffset)
|
||||||
lastResult.end = point.offset;
|
lastResult.end = point.offset;
|
||||||
else results.push({ start: lastOffset, end: point.offset });
|
else results.push({ start: lastOffset, end: point.offset });
|
||||||
|
@ -500,38 +500,38 @@ export class DOMWorld {
|
|||||||
options: { delay?: number; button?: MouseButton; clickCount?: number }
|
options: { delay?: number; button?: MouseButton; clickCount?: number }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
await handle!.click(options);
|
await handle.click(options);
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus(selector: string): Promise<void> {
|
async focus(selector: string): Promise<void> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
await handle!.focus();
|
await handle.focus();
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async hover(selector: string): Promise<void> {
|
async hover(selector: string): Promise<void> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
await handle!.hover();
|
await handle.hover();
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(selector: string, ...values: string[]): Promise<string[]> {
|
async select(selector: string, ...values: string[]): Promise<string[]> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
const result = await handle!.select(...values);
|
const result = await handle.select(...values);
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tap(selector: string): Promise<void> {
|
async tap(selector: string): Promise<void> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
await handle!.tap();
|
await handle.tap();
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async type(
|
async type(
|
||||||
@ -540,9 +540,9 @@ export class DOMWorld {
|
|||||||
options?: { delay: number }
|
options?: { delay: number }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, `No element found for selector: ${selector}`);
|
||||||
await handle!.type(text, options);
|
await handle.type(text, options);
|
||||||
await handle!.dispose();
|
await handle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(
|
async waitForSelector(
|
||||||
@ -551,9 +551,7 @@ export class DOMWorld {
|
|||||||
): Promise<ElementHandle | null> {
|
): Promise<ElementHandle | null> {
|
||||||
const { updatedSelector, queryHandler } =
|
const { updatedSelector, queryHandler } =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
if (!queryHandler.waitFor) {
|
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||||
throw new Error('Query handler does not support waitFor');
|
|
||||||
}
|
|
||||||
return queryHandler.waitFor(this, updatedSelector, options);
|
return queryHandler.waitFor(this, updatedSelector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -970,7 +968,7 @@ async function waitForPredicatePageFunction(
|
|||||||
root: Element | Document | null,
|
root: Element | Document | null,
|
||||||
predicateBody: string,
|
predicateBody: string,
|
||||||
predicateAcceptsContextElement: boolean,
|
predicateAcceptsContextElement: boolean,
|
||||||
polling: string,
|
polling: 'raf' | 'mutation' | number,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
...args: unknown[]
|
...args: unknown[]
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
@ -978,9 +976,14 @@ async function waitForPredicatePageFunction(
|
|||||||
const predicate = new Function('...args', predicateBody);
|
const predicate = new Function('...args', predicateBody);
|
||||||
let timedOut = false;
|
let timedOut = false;
|
||||||
if (timeout) setTimeout(() => (timedOut = true), timeout);
|
if (timeout) setTimeout(() => (timedOut = true), timeout);
|
||||||
if (polling === 'raf') return await pollRaf();
|
switch (polling) {
|
||||||
if (polling === 'mutation') return await pollMutation();
|
case 'raf':
|
||||||
if (typeof polling === 'number') return await pollInterval(polling);
|
return await pollRaf();
|
||||||
|
case 'mutation':
|
||||||
|
return await pollMutation();
|
||||||
|
default:
|
||||||
|
return await pollInterval(polling);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {!Promise<*>}
|
* @returns {!Promise<*>}
|
||||||
@ -1023,7 +1026,7 @@ async function waitForPredicatePageFunction(
|
|||||||
await onRaf();
|
await onRaf();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
async function onRaf(): Promise<unknown> {
|
async function onRaf(): Promise<void> {
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
fulfill();
|
fulfill();
|
||||||
return;
|
return;
|
||||||
@ -1042,7 +1045,7 @@ async function waitForPredicatePageFunction(
|
|||||||
await onTimeout();
|
await onTimeout();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
async function onTimeout(): Promise<unknown> {
|
async function onTimeout(): Promise<void> {
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
fulfill();
|
fulfill();
|
||||||
return;
|
return;
|
||||||
|
@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
import { isNode } from '../environment.js';
|
import { isNode } from '../environment.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var __PUPPETEER_DEBUG: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A debug function that can be used in any environment.
|
* A debug function that can be used in any environment.
|
||||||
*
|
*
|
||||||
@ -60,7 +65,7 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (...logArgs: unknown[]): void => {
|
return (...logArgs: unknown[]): void => {
|
||||||
const debugLevel = globalThis.__PUPPETEER_DEBUG as string;
|
const debugLevel = globalThis.__PUPPETEER_DEBUG;
|
||||||
if (!debugLevel) return;
|
if (!debugLevel) return;
|
||||||
|
|
||||||
const everythingShouldBeLogged = debugLevel === '*';
|
const everythingShouldBeLogged = debugLevel === '*';
|
||||||
|
@ -19,7 +19,9 @@ import { JSHandle, ElementHandle } from './JSHandle.js';
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type EvaluateFn<T = any> = string | ((arg1: T, ...args: any[]) => any);
|
export type EvaluateFn<T = any, U = any, V = any> =
|
||||||
|
| string
|
||||||
|
| ((arg1: T, ...args: U[]) => V);
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -47,7 +49,7 @@ export type Serializable =
|
|||||||
| string
|
| string
|
||||||
| boolean
|
| boolean
|
||||||
| null
|
| null
|
||||||
| BigInt
|
| bigint
|
||||||
| JSONArray
|
| JSONArray
|
||||||
| JSONObject;
|
| JSONObject;
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export class ExecutionContext {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_world: DOMWorld;
|
_world?: DOMWorld;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -69,7 +69,7 @@ export class ExecutionContext {
|
|||||||
constructor(
|
constructor(
|
||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
||||||
world: DOMWorld
|
world?: DOMWorld
|
||||||
) {
|
) {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._world = world;
|
this._world = world;
|
||||||
@ -277,12 +277,10 @@ export class ExecutionContext {
|
|||||||
? helper.valueFromRemoteObject(remoteObject)
|
? helper.valueFromRemoteObject(remoteObject)
|
||||||
: createJSHandle(this, remoteObject);
|
: createJSHandle(this, remoteObject);
|
||||||
|
|
||||||
/**
|
function convertArgument(
|
||||||
* @param {*} arg
|
this: ExecutionContext,
|
||||||
* @returns {*}
|
arg: unknown
|
||||||
* @this {ExecutionContext}
|
): Protocol.Runtime.CallArgument {
|
||||||
*/
|
|
||||||
function convertArgument(this: ExecutionContext, arg: unknown): unknown {
|
|
||||||
if (typeof arg === 'bigint')
|
if (typeof arg === 'bigint')
|
||||||
// eslint-disable-line valid-typeof
|
// eslint-disable-line valid-typeof
|
||||||
return { unserializableValue: `${arg.toString()}n` };
|
return { unserializableValue: `${arg.toString()}n` };
|
||||||
@ -364,7 +362,7 @@ export class ExecutionContext {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async _adoptBackendNodeId(
|
async _adoptBackendNodeId(
|
||||||
backendNodeId: Protocol.DOM.BackendNodeId
|
backendNodeId?: Protocol.DOM.BackendNodeId
|
||||||
): Promise<ElementHandle> {
|
): Promise<ElementHandle> {
|
||||||
const { object } = await this._client.send('DOM.resolveNode', {
|
const { object } = await this._client.send('DOM.resolveNode', {
|
||||||
backendNodeId: backendNodeId,
|
backendNodeId: backendNodeId,
|
||||||
|
@ -73,7 +73,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
private _frames = new Map<string, Frame>();
|
private _frames = new Map<string, Frame>();
|
||||||
private _contextIdToContext = new Map<string, ExecutionContext>();
|
private _contextIdToContext = new Map<string, ExecutionContext>();
|
||||||
private _isolatedWorlds = new Set<string>();
|
private _isolatedWorlds = new Set<string>();
|
||||||
private _mainFrame: Frame;
|
private _mainFrame?: Frame;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
@ -163,8 +163,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// The target might have been closed before the initialization finished.
|
// The target might have been closed before the initialization finished.
|
||||||
if (
|
if (
|
||||||
error.message.includes('Target closed') ||
|
error instanceof Error &&
|
||||||
error.message.includes('Session closed')
|
(error.message.includes('Target closed') ||
|
||||||
|
error.message.includes('Session closed'))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -212,7 +213,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
async function navigate(
|
async function navigate(
|
||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
url: string,
|
url: string,
|
||||||
referrer: string,
|
referrer: string | undefined,
|
||||||
frameId: string
|
frameId: string
|
||||||
): Promise<Error | null> {
|
): Promise<Error | null> {
|
||||||
try {
|
try {
|
||||||
@ -225,7 +226,10 @@ export class FrameManager extends EventEmitter {
|
|||||||
? new Error(`${response.errorText} at ${url}`)
|
? new Error(`${response.errorText} at ${url}`)
|
||||||
: null;
|
: null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
if (error instanceof Error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,9 +265,10 @@ export class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const frame = this._frames.get(event.targetInfo.targetId);
|
const frame = this._frames.get(event.targetInfo.targetId);
|
||||||
const session = Connection.fromSession(this._client).session(
|
const connection = Connection.fromSession(this._client);
|
||||||
event.sessionId
|
assert(connection);
|
||||||
);
|
const session = connection.session(event.sessionId);
|
||||||
|
assert(session);
|
||||||
if (frame) frame._updateClient(session);
|
if (frame) frame._updateClient(session);
|
||||||
this.setupEventListeners(session);
|
this.setupEventListeners(session);
|
||||||
await this.initialize(session);
|
await this.initialize(session);
|
||||||
@ -272,6 +277,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
private async _onDetachedFromTarget(
|
private async _onDetachedFromTarget(
|
||||||
event: Protocol.Target.DetachedFromTargetEvent
|
event: Protocol.Target.DetachedFromTargetEvent
|
||||||
) {
|
) {
|
||||||
|
if (!event.targetId) return;
|
||||||
const frame = this._frames.get(event.targetId);
|
const frame = this._frames.get(event.targetId);
|
||||||
if (frame && frame.isOOPFrame()) {
|
if (frame && frame.isOOPFrame()) {
|
||||||
// When an OOP iframe is removed from the page, it
|
// When an OOP iframe is removed from the page, it
|
||||||
@ -324,6 +330,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainFrame(): Frame {
|
mainFrame(): Frame {
|
||||||
|
assert(this._mainFrame, 'Requesting main frame too early!');
|
||||||
return this._mainFrame;
|
return this._mainFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +348,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
parentFrameId?: string
|
parentFrameId?: string
|
||||||
): void {
|
): void {
|
||||||
if (this._frames.has(frameId)) {
|
if (this._frames.has(frameId)) {
|
||||||
const frame = this._frames.get(frameId);
|
const frame = this._frames.get(frameId)!;
|
||||||
if (session && frame.isOOPFrame()) {
|
if (session && frame.isOOPFrame()) {
|
||||||
// If an OOP iframes becomes a normal iframe again
|
// If an OOP iframes becomes a normal iframe again
|
||||||
// it is first attached to the parent page before
|
// it is first attached to the parent page before
|
||||||
@ -352,6 +359,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
|
assert(parentFrame);
|
||||||
const frame = new Frame(this, parentFrame, frameId, session);
|
const frame = new Frame(this, parentFrame, frameId, session);
|
||||||
this._frames.set(frame._id, frame);
|
this._frames.set(frame._id, frame);
|
||||||
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
||||||
@ -388,6 +396,7 @@ export class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update frame payload.
|
// Update frame payload.
|
||||||
|
assert(frame);
|
||||||
frame._navigated(framePayload);
|
frame._navigated(framePayload);
|
||||||
|
|
||||||
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
||||||
@ -445,10 +454,11 @@ export class FrameManager extends EventEmitter {
|
|||||||
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
||||||
session: CDPSession
|
session: CDPSession
|
||||||
): void {
|
): void {
|
||||||
const auxData = contextPayload.auxData as { frameId?: string };
|
const auxData = contextPayload.auxData as { frameId?: string } | undefined;
|
||||||
const frameId = auxData ? auxData.frameId : null;
|
const frameId = auxData && auxData.frameId;
|
||||||
const frame = this._frames.get(frameId) || null;
|
const frame =
|
||||||
let world = null;
|
typeof frameId === 'string' ? this._frames.get(frameId) : undefined;
|
||||||
|
let world: DOMWorld | undefined;
|
||||||
if (frame) {
|
if (frame) {
|
||||||
// Only care about execution contexts created for the current session.
|
// Only care about execution contexts created for the current session.
|
||||||
if (frame._client !== session) return;
|
if (frame._client !== session) return;
|
||||||
@ -640,7 +650,7 @@ export class Frame {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_frameManager: FrameManager;
|
_frameManager: FrameManager;
|
||||||
private _parentFrame?: Frame;
|
private _parentFrame: Frame | null;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -668,11 +678,11 @@ export class Frame {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_mainWorld: DOMWorld;
|
_mainWorld!: DOMWorld;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_secondaryWorld: DOMWorld;
|
_secondaryWorld!: DOMWorld;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -680,7 +690,7 @@ export class Frame {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_client: CDPSession;
|
_client!: CDPSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -692,7 +702,7 @@ export class Frame {
|
|||||||
client: CDPSession
|
client: CDPSession
|
||||||
) {
|
) {
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
this._parentFrame = parentFrame;
|
this._parentFrame = parentFrame ?? null;
|
||||||
this._url = '';
|
this._url = '';
|
||||||
this._id = frameId;
|
this._id = frameId;
|
||||||
this._detached = false;
|
this._detached = false;
|
||||||
@ -1457,7 +1467,7 @@ function assertNoLegacyNavigationOptions(options: {
|
|||||||
'ERROR: networkIdleInflight option is no longer supported.'
|
'ERROR: networkIdleInflight option is no longer supported.'
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
options.waitUntil !== 'networkidle',
|
options['waitUntil'] !== 'networkidle',
|
||||||
'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead'
|
'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -185,8 +185,8 @@ export class HTTPRequest {
|
|||||||
this._interceptHandlers = [];
|
this._interceptHandlers = [];
|
||||||
this._initiator = event.initiator;
|
this._initiator = event.initiator;
|
||||||
|
|
||||||
for (const key of Object.keys(event.request.headers))
|
for (const [key, value] of Object.entries(event.request.headers))
|
||||||
this._headers[key.toLowerCase()] = event.request.headers[key];
|
this._headers[key.toLowerCase()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,8 +88,9 @@ export class HTTPResponse {
|
|||||||
|
|
||||||
this._status = extraInfo ? extraInfo.statusCode : responsePayload.status;
|
this._status = extraInfo ? extraInfo.statusCode : responsePayload.status;
|
||||||
const headers = extraInfo ? extraInfo.headers : responsePayload.headers;
|
const headers = extraInfo ? extraInfo.headers : responsePayload.headers;
|
||||||
for (const key of Object.keys(headers))
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
this._headers[key.toLowerCase()] = headers[key];
|
this._headers[key.toLowerCase()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
this._securityDetails = responsePayload.securityDetails
|
this._securityDetails = responsePayload.securityDetails
|
||||||
? new SecurityDetails(responsePayload.securityDetails)
|
? new SecurityDetails(responsePayload.securityDetails)
|
||||||
|
@ -38,10 +38,10 @@ import { MouseButton } from './Input.js';
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface BoxModel {
|
export interface BoxModel {
|
||||||
content: Array<{ x: number; y: number }>;
|
content: Point[];
|
||||||
padding: Array<{ x: number; y: number }>;
|
padding: Point[];
|
||||||
border: Array<{ x: number; y: number }>;
|
border: Point[];
|
||||||
margin: Array<{ x: number; y: number }>;
|
margin: Point[];
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
@ -49,15 +49,7 @@ export interface BoxModel {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface BoundingBox {
|
export interface BoundingBox extends Point {
|
||||||
/**
|
|
||||||
* the x coordinate of the element in pixels.
|
|
||||||
*/
|
|
||||||
x: number;
|
|
||||||
/**
|
|
||||||
* the y coordinate of the element in pixels.
|
|
||||||
*/
|
|
||||||
y: number;
|
|
||||||
/**
|
/**
|
||||||
* the width of the element in pixels.
|
* the width of the element in pixels.
|
||||||
*/
|
*/
|
||||||
@ -90,11 +82,8 @@ export function createJSHandle(
|
|||||||
return new JSHandle(context, context._client, remoteObject);
|
return new JSHandle(context, context._client, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyOffsetsToQuad = (
|
const applyOffsetsToQuad = (quad: Point[], offsetX: number, offsetY: number) =>
|
||||||
quad: Array<{ x: number; y: number }>,
|
quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY }));
|
||||||
offsetX: number,
|
|
||||||
offsetY: number
|
|
||||||
) => quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY }));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an in-page JavaScript object. JSHandles can be created with the
|
* Represents an in-page JavaScript object. JSHandles can be created with the
|
||||||
@ -202,8 +191,8 @@ export class JSHandle<HandleObjectType = unknown> {
|
|||||||
*/
|
*/
|
||||||
async getProperty(propertyName: string): Promise<JSHandle> {
|
async getProperty(propertyName: string): Promise<JSHandle> {
|
||||||
const objectHandle = await this.evaluateHandle(
|
const objectHandle = await this.evaluateHandle(
|
||||||
(object: Element, propertyName: string) => {
|
(object: Element, propertyName: keyof Element) => {
|
||||||
const result = { __proto__: null };
|
const result: Record<string, unknown> = { __proto__: null };
|
||||||
result[propertyName] = object[propertyName];
|
result[propertyName] = object[propertyName];
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
@ -234,13 +223,14 @@ export class JSHandle<HandleObjectType = unknown> {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async getProperties(): Promise<Map<string, JSHandle>> {
|
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||||
|
assert(this._remoteObject.objectId);
|
||||||
const response = await this._client.send('Runtime.getProperties', {
|
const response = await this._client.send('Runtime.getProperties', {
|
||||||
objectId: this._remoteObject.objectId,
|
objectId: this._remoteObject.objectId,
|
||||||
ownProperties: true,
|
ownProperties: true,
|
||||||
});
|
});
|
||||||
const result = new Map<string, JSHandle>();
|
const result = new Map<string, JSHandle>();
|
||||||
for (const property of response.result) {
|
for (const property of response.result) {
|
||||||
if (!property.enumerable) continue;
|
if (!property.enumerable || !property.value) continue;
|
||||||
result.set(property.name, createJSHandle(this._context, property.value));
|
result.set(property.name, createJSHandle(this._context, property.value));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -402,6 +392,7 @@ export class ElementHandle<
|
|||||||
} = {}
|
} = {}
|
||||||
): Promise<ElementHandle | null> {
|
): Promise<ElementHandle | null> {
|
||||||
const frame = this._context.frame();
|
const frame = this._context.frame();
|
||||||
|
assert(frame);
|
||||||
const secondaryContext = await frame._secondaryWorld.executionContext();
|
const secondaryContext = await frame._secondaryWorld.executionContext();
|
||||||
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
|
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
|
||||||
const handle = await frame._secondaryWorld.waitForSelector(selector, {
|
const handle = await frame._secondaryWorld.waitForSelector(selector, {
|
||||||
@ -475,6 +466,7 @@ export class ElementHandle<
|
|||||||
} = {}
|
} = {}
|
||||||
): Promise<ElementHandle | null> {
|
): Promise<ElementHandle | null> {
|
||||||
const frame = this._context.frame();
|
const frame = this._context.frame();
|
||||||
|
assert(frame);
|
||||||
const secondaryContext = await frame._secondaryWorld.executionContext();
|
const secondaryContext = await frame._secondaryWorld.executionContext();
|
||||||
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
|
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
|
||||||
xpath = xpath.startsWith('//') ? '.' + xpath : xpath;
|
xpath = xpath.startsWith('//') ? '.' + xpath : xpath;
|
||||||
@ -494,7 +486,7 @@ export class ElementHandle<
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement(): ElementHandle<ElementType> | null {
|
override asElement(): ElementHandle<ElementType> | null {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,46 +503,47 @@ export class ElementHandle<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
||||||
const error = await this.evaluate<
|
const error = await this.evaluate(
|
||||||
(
|
async (
|
||||||
element: Element,
|
element: Element,
|
||||||
pageJavascriptEnabled: boolean
|
pageJavascriptEnabled: boolean
|
||||||
) => Promise<string | false>
|
): Promise<string | false> => {
|
||||||
>(async (element, pageJavascriptEnabled) => {
|
if (!element.isConnected) return 'Node is detached from document';
|
||||||
if (!element.isConnected) return 'Node is detached from document';
|
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
return 'Node is not of type HTMLElement';
|
||||||
return 'Node is not of type HTMLElement';
|
// force-scroll if page's javascript is disabled.
|
||||||
// force-scroll if page's javascript is disabled.
|
if (!pageJavascriptEnabled) {
|
||||||
if (!pageJavascriptEnabled) {
|
element.scrollIntoView({
|
||||||
element.scrollIntoView({
|
block: 'center',
|
||||||
block: 'center',
|
inline: 'center',
|
||||||
inline: 'center',
|
// @ts-expect-error Chrome still supports behavior: instant but
|
||||||
// @ts-expect-error Chrome still supports behavior: instant but
|
// it's not in the spec so TS shouts We don't want to make this
|
||||||
// it's not in the spec so TS shouts We don't want to make this
|
// breaking change in Puppeteer yet so we'll ignore the line.
|
||||||
// breaking change in Puppeteer yet so we'll ignore the line.
|
behavior: 'instant',
|
||||||
behavior: 'instant',
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const visibleRatio = await new Promise((resolve) => {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
resolve(entries[0]!.intersectionRatio);
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
observer.observe(element);
|
||||||
});
|
});
|
||||||
|
if (visibleRatio !== 1.0) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
block: 'center',
|
||||||
|
inline: 'center',
|
||||||
|
// @ts-expect-error Chrome still supports behavior: instant but
|
||||||
|
// it's not in the spec so TS shouts We don't want to make this
|
||||||
|
// breaking change in Puppeteer yet so we'll ignore the line.
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
const visibleRatio = await new Promise((resolve) => {
|
this._page.isJavaScriptEnabled()
|
||||||
const observer = new IntersectionObserver((entries) => {
|
);
|
||||||
resolve(entries[0].intersectionRatio);
|
|
||||||
observer.disconnect();
|
|
||||||
});
|
|
||||||
observer.observe(element);
|
|
||||||
});
|
|
||||||
if (visibleRatio !== 1.0) {
|
|
||||||
element.scrollIntoView({
|
|
||||||
block: 'center',
|
|
||||||
inline: 'center',
|
|
||||||
// @ts-expect-error Chrome still supports behavior: instant but
|
|
||||||
// it's not in the spec so TS shouts We don't want to make this
|
|
||||||
// breaking change in Puppeteer yet so we'll ignore the line.
|
|
||||||
behavior: 'instant',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, this._page.isJavaScriptEnabled());
|
|
||||||
|
|
||||||
if (error) throw new Error(error);
|
if (error) throw new Error(error);
|
||||||
}
|
}
|
||||||
@ -560,14 +553,15 @@ export class ElementHandle<
|
|||||||
): Promise<{ offsetX: number; offsetY: number }> {
|
): Promise<{ offsetX: number; offsetY: number }> {
|
||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
while (frame.parentFrame()) {
|
let currentFrame: Frame | null = frame;
|
||||||
const parent = frame.parentFrame();
|
while (currentFrame && currentFrame.parentFrame()) {
|
||||||
if (!frame.isOOPFrame()) {
|
const parent = currentFrame.parentFrame();
|
||||||
frame = parent;
|
if (!currentFrame.isOOPFrame() || !parent) {
|
||||||
|
currentFrame = parent;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { backendNodeId } = await parent._client.send('DOM.getFrameOwner', {
|
const { backendNodeId } = await parent._client.send('DOM.getFrameOwner', {
|
||||||
frameId: frame._id,
|
frameId: currentFrame._id,
|
||||||
});
|
});
|
||||||
const result = await parent._client.send('DOM.getBoxModel', {
|
const result = await parent._client.send('DOM.getBoxModel', {
|
||||||
backendNodeId: backendNodeId,
|
backendNodeId: backendNodeId,
|
||||||
@ -577,9 +571,9 @@ export class ElementHandle<
|
|||||||
}
|
}
|
||||||
const contentBoxQuad = result.model.content;
|
const contentBoxQuad = result.model.content;
|
||||||
const topLeftCorner = this._fromProtocolQuad(contentBoxQuad)[0];
|
const topLeftCorner = this._fromProtocolQuad(contentBoxQuad)[0];
|
||||||
offsetX += topLeftCorner.x;
|
offsetX += topLeftCorner!.x;
|
||||||
offsetY += topLeftCorner.y;
|
offsetY += topLeftCorner!.y;
|
||||||
frame = parent;
|
currentFrame = parent;
|
||||||
}
|
}
|
||||||
return { offsetX, offsetY };
|
return { offsetX, offsetY };
|
||||||
}
|
}
|
||||||
@ -612,7 +606,7 @@ export class ElementHandle<
|
|||||||
.filter((quad) => computeQuadArea(quad) > 1);
|
.filter((quad) => computeQuadArea(quad) > 1);
|
||||||
if (!quads.length)
|
if (!quads.length)
|
||||||
throw new Error('Node is either not clickable or not an HTMLElement');
|
throw new Error('Node is either not clickable or not an HTMLElement');
|
||||||
const quad = quads[0];
|
const quad = quads[0]!;
|
||||||
if (offset) {
|
if (offset) {
|
||||||
// Return the point of the first quad identified by offset.
|
// Return the point of the first quad identified by offset.
|
||||||
let minX = Number.MAX_SAFE_INTEGER;
|
let minX = Number.MAX_SAFE_INTEGER;
|
||||||
@ -657,20 +651,20 @@ export class ElementHandle<
|
|||||||
.catch((error) => debugError(error));
|
.catch((error) => debugError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _fromProtocolQuad(quad: number[]): Array<{ x: number; y: number }> {
|
private _fromProtocolQuad(quad: number[]): Point[] {
|
||||||
return [
|
return [
|
||||||
{ x: quad[0], y: quad[1] },
|
{ x: quad[0]!, y: quad[1]! },
|
||||||
{ x: quad[2], y: quad[3] },
|
{ x: quad[2]!, y: quad[3]! },
|
||||||
{ x: quad[4], y: quad[5] },
|
{ x: quad[4]!, y: quad[5]! },
|
||||||
{ x: quad[6], y: quad[7] },
|
{ x: quad[6]!, y: quad[7]! },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _intersectQuadWithViewport(
|
private _intersectQuadWithViewport(
|
||||||
quad: Array<{ x: number; y: number }>,
|
quad: Point[],
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
): Array<{ x: number; y: number }> {
|
): Point[] {
|
||||||
return quad.map((point) => ({
|
return quad.map((point) => ({
|
||||||
x: Math.min(Math.max(point.x, 0), width),
|
x: Math.min(Math.max(point.x, 0), width),
|
||||||
y: Math.min(Math.max(point.y, 0), height),
|
y: Math.min(Math.max(point.y, 0), height),
|
||||||
@ -789,7 +783,7 @@ export class ElementHandle<
|
|||||||
throw new Error('Element is not a <select> element.');
|
throw new Error('Element is not a <select> element.');
|
||||||
|
|
||||||
const options = Array.from(element.options);
|
const options = Array.from(element.options);
|
||||||
element.value = undefined;
|
element.value = '';
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
option.selected = values.includes(option.value);
|
option.selected = values.includes(option.value);
|
||||||
if (option.selected && !element.multiple) break;
|
if (option.selected && !element.multiple) break;
|
||||||
@ -843,7 +837,7 @@ export class ElementHandle<
|
|||||||
try {
|
try {
|
||||||
await fs.promises.access(resolvedPath, fs.constants.R_OK);
|
await fs.promises.access(resolvedPath, fs.constants.R_OK);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT')
|
if (error && (error as NodeJS.ErrnoException).code === 'ENOENT')
|
||||||
throw new Error(`${filePath} does not exist or is not readable`);
|
throw new Error(`${filePath} does not exist or is not readable`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,10 +946,10 @@ export class ElementHandle<
|
|||||||
|
|
||||||
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
||||||
const quad = result.model.border;
|
const quad = result.model.border;
|
||||||
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
|
const x = Math.min(quad[0]!, quad[2]!, quad[4]!, quad[6]!);
|
||||||
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
|
const y = Math.min(quad[1]!, quad[3]!, quad[5]!, quad[7]!);
|
||||||
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
|
const width = Math.max(quad[0]!, quad[2]!, quad[4]!, quad[6]!) - x;
|
||||||
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
|
const height = Math.max(quad[1]!, quad[3]!, quad[5]!, quad[7]!) - y;
|
||||||
|
|
||||||
return { x: x + offsetX, y: y + offsetY, width, height };
|
return { x: x + offsetX, y: y + offsetY, width, height };
|
||||||
}
|
}
|
||||||
@ -1014,11 +1008,11 @@ export class ElementHandle<
|
|||||||
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
||||||
|
|
||||||
const viewport = this._page.viewport();
|
const viewport = this._page.viewport();
|
||||||
|
assert(viewport);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
viewport &&
|
boundingBox.width > viewport.width ||
|
||||||
(boundingBox.width > viewport.width ||
|
boundingBox.height > viewport.height
|
||||||
boundingBox.height > viewport.height)
|
|
||||||
) {
|
) {
|
||||||
const newViewport = {
|
const newViewport = {
|
||||||
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
|
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
|
||||||
@ -1061,14 +1055,21 @@ export class ElementHandle<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs `element.querySelector` within the page. If no element matches the selector,
|
* Runs `element.querySelector` within the page.
|
||||||
* the return value resolves to `null`.
|
*
|
||||||
|
* @param selector The selector to query with.
|
||||||
|
* @returns `null` if no element matches the selector.
|
||||||
|
* @throws `Error` if the selector has no associated query handler.
|
||||||
*/
|
*/
|
||||||
async $<T extends Element = Element>(
|
async $<T extends Element = Element>(
|
||||||
selector: string
|
selector: string
|
||||||
): Promise<ElementHandle<T> | null> {
|
): Promise<ElementHandle<T> | null> {
|
||||||
const { updatedSelector, queryHandler } =
|
const { updatedSelector, queryHandler } =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
|
assert(
|
||||||
|
queryHandler.queryOne,
|
||||||
|
'Cannot handle queries for a single element with the given selector'
|
||||||
|
);
|
||||||
return queryHandler.queryOne(this, updatedSelector);
|
return queryHandler.queryOne(this, updatedSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,11 +1077,22 @@ export class ElementHandle<
|
|||||||
* Runs `element.querySelectorAll` within the page. If no elements match the selector,
|
* Runs `element.querySelectorAll` within the page. If no elements match the selector,
|
||||||
* the return value resolves to `[]`.
|
* the return value resolves to `[]`.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Runs `element.querySelectorAll` within the page.
|
||||||
|
*
|
||||||
|
* @param selector The selector to query with.
|
||||||
|
* @returns `[]` if no element matches the selector.
|
||||||
|
* @throws `Error` if the selector has no associated query handler.
|
||||||
|
*/
|
||||||
async $$<T extends Element = Element>(
|
async $$<T extends Element = Element>(
|
||||||
selector: string
|
selector: string
|
||||||
): Promise<Array<ElementHandle<T>>> {
|
): Promise<Array<ElementHandle<T>>> {
|
||||||
const { updatedSelector, queryHandler } =
|
const { updatedSelector, queryHandler } =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
|
assert(
|
||||||
|
queryHandler.queryAll,
|
||||||
|
'Cannot handle queries for a multiple element with the given selector'
|
||||||
|
);
|
||||||
return queryHandler.queryAll(this, updatedSelector);
|
return queryHandler.queryAll(this, updatedSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1156,21 +1168,21 @@ export class ElementHandle<
|
|||||||
*/
|
*/
|
||||||
async $$eval<ReturnType>(
|
async $$eval<ReturnType>(
|
||||||
selector: string,
|
selector: string,
|
||||||
pageFunction: (
|
pageFunction: EvaluateFn<
|
||||||
elements: Element[],
|
Element[],
|
||||||
...args: unknown[]
|
unknown,
|
||||||
) => ReturnType | Promise<ReturnType>,
|
ReturnType | Promise<ReturnType>
|
||||||
|
>,
|
||||||
...args: SerializableOrJSHandle[]
|
...args: SerializableOrJSHandle[]
|
||||||
): Promise<WrapElementHandle<ReturnType>> {
|
): Promise<WrapElementHandle<ReturnType>> {
|
||||||
const { updatedSelector, queryHandler } =
|
const { updatedSelector, queryHandler } =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
|
assert(queryHandler.queryAllArray);
|
||||||
const arrayHandle = await queryHandler.queryAllArray(this, updatedSelector);
|
const arrayHandle = await queryHandler.queryAllArray(this, updatedSelector);
|
||||||
const result = await arrayHandle.evaluate<
|
const result = await arrayHandle.evaluate<EvaluateFn<Element[]>>(
|
||||||
(
|
pageFunction,
|
||||||
elements: Element[],
|
...args
|
||||||
...args: unknown[]
|
);
|
||||||
) => ReturnType | Promise<ReturnType>
|
|
||||||
>(pageFunction, ...args);
|
|
||||||
await arrayHandle.dispose();
|
await arrayHandle.dispose();
|
||||||
/* This `as` exists for the same reason as the `as` in $eval above.
|
/* This `as` exists for the same reason as the `as` in $eval above.
|
||||||
* See the comment there for a full explanation.
|
* See the comment there for a full explanation.
|
||||||
@ -1220,7 +1232,7 @@ export class ElementHandle<
|
|||||||
return await this.evaluate(async (element: Element, threshold: number) => {
|
return await this.evaluate(async (element: Element, threshold: number) => {
|
||||||
const visibleRatio = await new Promise<number>((resolve) => {
|
const visibleRatio = await new Promise<number>((resolve) => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
resolve(entries[0].intersectionRatio);
|
resolve(entries[0]!.intersectionRatio);
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
});
|
});
|
||||||
observer.observe(element);
|
observer.observe(element);
|
||||||
@ -1290,14 +1302,14 @@ export interface Point {
|
|||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeQuadArea(quad: Array<{ x: number; y: number }>): number {
|
function computeQuadArea(quad: Point[]): number {
|
||||||
/* Compute sum of all directed areas of adjacent triangles
|
/* Compute sum of all directed areas of adjacent triangles
|
||||||
https://en.wikipedia.org/wiki/Polygon#Simple_polygons
|
https://en.wikipedia.org/wiki/Polygon#Simple_polygons
|
||||||
*/
|
*/
|
||||||
let area = 0;
|
let area = 0;
|
||||||
for (let i = 0; i < quad.length; ++i) {
|
for (let i = 0; i < quad.length; ++i) {
|
||||||
const p1 = quad[i];
|
const p1 = quad[i]!;
|
||||||
const p2 = quad[(i + 1) % quad.length];
|
const p2 = quad[(i + 1) % quad.length]!;
|
||||||
area += (p1.x * p2.y - p2.x * p1.y) / 2;
|
area += (p1.x * p2.y - p2.x * p1.y) / 2;
|
||||||
}
|
}
|
||||||
return Math.abs(area);
|
return Math.abs(area);
|
||||||
|
@ -24,7 +24,7 @@ export interface PDFMargin {
|
|||||||
right?: string | number;
|
right?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LowerCasePaperFormat =
|
export type LowerCasePaperFormat =
|
||||||
| 'letter'
|
| 'letter'
|
||||||
| 'legal'
|
| 'legal'
|
||||||
| 'tabloid'
|
| 'tabloid'
|
||||||
|
@ -14,55 +14,58 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Protocol } from 'devtools-protocol';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
|
import { isNode } from '../environment.js';
|
||||||
import { EventEmitter, Handler } from './EventEmitter.js';
|
import { Accessibility } from './Accessibility.js';
|
||||||
|
import { assert, assertNever } from './assert.js';
|
||||||
|
import { Browser, BrowserContext } from './Browser.js';
|
||||||
import {
|
import {
|
||||||
Connection,
|
|
||||||
CDPSession,
|
CDPSession,
|
||||||
CDPSessionEmittedEvents,
|
CDPSessionEmittedEvents,
|
||||||
|
Connection,
|
||||||
} from './Connection.js';
|
} from './Connection.js';
|
||||||
|
import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage.js';
|
||||||
|
import { Coverage } from './Coverage.js';
|
||||||
import { Dialog } from './Dialog.js';
|
import { Dialog } from './Dialog.js';
|
||||||
import { EmulationManager } from './EmulationManager.js';
|
import { EmulationManager } from './EmulationManager.js';
|
||||||
|
import {
|
||||||
|
EvaluateFn,
|
||||||
|
EvaluateFnReturnType,
|
||||||
|
EvaluateHandleFn,
|
||||||
|
SerializableOrJSHandle,
|
||||||
|
UnwrapPromiseLike,
|
||||||
|
WrapElementHandle,
|
||||||
|
} from './EvalTypes.js';
|
||||||
|
import { EventEmitter, Handler } from './EventEmitter.js';
|
||||||
|
import { FileChooser } from './FileChooser.js';
|
||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
FrameManager,
|
FrameManager,
|
||||||
FrameManagerEmittedEvents,
|
FrameManagerEmittedEvents,
|
||||||
} from './FrameManager.js';
|
} from './FrameManager.js';
|
||||||
import { Keyboard, Mouse, Touchscreen, MouseButton } from './Input.js';
|
import { debugError, helper } from './helper.js';
|
||||||
import { Tracing } from './Tracing.js';
|
import { HTTPRequest } from './HTTPRequest.js';
|
||||||
import { assert, assertNever } from './assert.js';
|
import { HTTPResponse } from './HTTPResponse.js';
|
||||||
import { helper, debugError } from './helper.js';
|
import { Keyboard, Mouse, MouseButton, Touchscreen } from './Input.js';
|
||||||
import { Coverage } from './Coverage.js';
|
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle.js';
|
||||||
import { WebWorker } from './WebWorker.js';
|
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher.js';
|
||||||
import { Browser, BrowserContext } from './Browser.js';
|
|
||||||
import { Target } from './Target.js';
|
|
||||||
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle.js';
|
|
||||||
import { Viewport } from './PuppeteerViewport.js';
|
|
||||||
import {
|
import {
|
||||||
Credentials,
|
Credentials,
|
||||||
NetworkConditions,
|
NetworkConditions,
|
||||||
NetworkManagerEmittedEvents,
|
NetworkManagerEmittedEvents,
|
||||||
} from './NetworkManager.js';
|
} from './NetworkManager.js';
|
||||||
import { HTTPRequest } from './HTTPRequest.js';
|
|
||||||
import { HTTPResponse } from './HTTPResponse.js';
|
|
||||||
import { Accessibility } from './Accessibility.js';
|
|
||||||
import { TimeoutSettings } from './TimeoutSettings.js';
|
|
||||||
import { FileChooser } from './FileChooser.js';
|
|
||||||
import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage.js';
|
|
||||||
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher.js';
|
|
||||||
import { Protocol } from 'devtools-protocol';
|
|
||||||
import {
|
import {
|
||||||
SerializableOrJSHandle,
|
LowerCasePaperFormat,
|
||||||
EvaluateHandleFn,
|
paperFormats,
|
||||||
WrapElementHandle,
|
PDFOptions,
|
||||||
EvaluateFn,
|
} from './PDFOptions.js';
|
||||||
EvaluateFnReturnType,
|
import { Viewport } from './PuppeteerViewport.js';
|
||||||
UnwrapPromiseLike,
|
import { Target } from './Target.js';
|
||||||
} from './EvalTypes.js';
|
|
||||||
import { PDFOptions, paperFormats } from './PDFOptions.js';
|
|
||||||
import { isNode } from '../environment.js';
|
|
||||||
import { TaskQueue } from './TaskQueue.js';
|
import { TaskQueue } from './TaskQueue.js';
|
||||||
|
import { TimeoutSettings } from './TimeoutSettings.js';
|
||||||
|
import { Tracing } from './Tracing.js';
|
||||||
|
import { WebWorker } from './WebWorker.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -492,35 +495,35 @@ export class Page extends EventEmitter {
|
|||||||
client.on(
|
client.on(
|
||||||
'Target.attachedToTarget',
|
'Target.attachedToTarget',
|
||||||
(event: Protocol.Target.AttachedToTargetEvent) => {
|
(event: Protocol.Target.AttachedToTargetEvent) => {
|
||||||
if (
|
switch (event.targetInfo.type) {
|
||||||
event.targetInfo.type !== 'worker' &&
|
case 'worker':
|
||||||
event.targetInfo.type !== 'iframe'
|
const connection = Connection.fromSession(client);
|
||||||
) {
|
assert(connection);
|
||||||
// If we don't detach from service workers, they will never die.
|
const session = connection.session(event.sessionId);
|
||||||
// We still want to attach to workers for emitting events.
|
assert(session);
|
||||||
// We still want to attach to iframes so sessions may interact with them.
|
const worker = new WebWorker(
|
||||||
// We detach from all other types out of an abundance of caution.
|
session,
|
||||||
// See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
|
event.targetInfo.url,
|
||||||
// for the complete list of available types.
|
this._addConsoleMessage.bind(this),
|
||||||
client
|
this._handleException.bind(this)
|
||||||
.send('Target.detachFromTarget', {
|
);
|
||||||
sessionId: event.sessionId,
|
this._workers.set(event.sessionId, worker);
|
||||||
})
|
this.emit(PageEmittedEvents.WorkerCreated, worker);
|
||||||
.catch(debugError);
|
break;
|
||||||
return;
|
case 'iframe':
|
||||||
}
|
break;
|
||||||
if (event.targetInfo.type === 'worker') {
|
default:
|
||||||
const session = Connection.fromSession(client).session(
|
// If we don't detach from service workers, they will never die.
|
||||||
event.sessionId
|
// We still want to attach to workers for emitting events.
|
||||||
);
|
// We still want to attach to iframes so sessions may interact with them.
|
||||||
const worker = new WebWorker(
|
// We detach from all other types out of an abundance of caution.
|
||||||
session,
|
// See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
|
||||||
event.targetInfo.url,
|
// for the complete list of available types.
|
||||||
this._addConsoleMessage.bind(this),
|
client
|
||||||
this._handleException.bind(this)
|
.send('Target.detachFromTarget', {
|
||||||
);
|
sessionId: event.sessionId,
|
||||||
this._workers.set(event.sessionId, worker);
|
})
|
||||||
this.emit(PageEmittedEvents.WorkerCreated, worker);
|
.catch(debugError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -598,6 +601,7 @@ export class Page extends EventEmitter {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this._fileChooserInterceptors.size) return;
|
if (!this._fileChooserInterceptors.size) return;
|
||||||
const frame = this._frameManager.frame(event.frameId);
|
const frame = this._frameManager.frame(event.frameId);
|
||||||
|
assert(frame);
|
||||||
const context = await frame.executionContext();
|
const context = await frame.executionContext();
|
||||||
const element = await context._adoptBackendNodeId(event.backendNodeId);
|
const element = await context._adoptBackendNodeId(event.backendNodeId);
|
||||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||||
@ -626,7 +630,7 @@ export class Page extends EventEmitter {
|
|||||||
// Note: this method exists to define event typings and handle
|
// Note: this method exists to define event typings and handle
|
||||||
// proper wireup of cooperative request interception. Actual event listening and
|
// proper wireup of cooperative request interception. Actual event listening and
|
||||||
// dispatching is delegated to EventEmitter.
|
// dispatching is delegated to EventEmitter.
|
||||||
public on<K extends keyof PageEventObject>(
|
public override on<K extends keyof PageEventObject>(
|
||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: PageEventObject[K]) => void
|
handler: (event: PageEventObject[K]) => void
|
||||||
): EventEmitter {
|
): EventEmitter {
|
||||||
@ -646,7 +650,7 @@ export class Page extends EventEmitter {
|
|||||||
return super.on(eventName, handler);
|
return super.on(eventName, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public once<K extends keyof PageEventObject>(
|
public override once<K extends keyof PageEventObject>(
|
||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: PageEventObject[K]) => void
|
handler: (event: PageEventObject[K]) => void
|
||||||
): EventEmitter {
|
): EventEmitter {
|
||||||
@ -655,7 +659,7 @@ export class Page extends EventEmitter {
|
|||||||
return super.once(eventName, handler);
|
return super.once(eventName, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
off<K extends keyof PageEventObject>(
|
override off<K extends keyof PageEventObject>(
|
||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: PageEventObject[K]) => void
|
handler: (event: PageEventObject[K]) => void
|
||||||
): EventEmitter {
|
): EventEmitter {
|
||||||
@ -697,7 +701,7 @@ export class Page extends EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { timeout = this._timeoutSettings.timeout() } = options;
|
const { timeout = this._timeoutSettings.timeout() } = options;
|
||||||
let callback: (value: FileChooser | PromiseLike<FileChooser>) => void;
|
let callback!: (value: FileChooser | PromiseLike<FileChooser>) => void;
|
||||||
const promise = new Promise<FileChooser>((x) => (callback = x));
|
const promise = new Promise<FileChooser>((x) => (callback = x));
|
||||||
this._fileChooserInterceptors.add(callback);
|
this._fileChooserInterceptors.add(callback);
|
||||||
return helper
|
return helper
|
||||||
@ -1253,7 +1257,9 @@ export class Page extends EventEmitter {
|
|||||||
const filterUnsupportedAttributes = (
|
const filterUnsupportedAttributes = (
|
||||||
cookie: Protocol.Network.Cookie
|
cookie: Protocol.Network.Cookie
|
||||||
): Protocol.Network.Cookie => {
|
): Protocol.Network.Cookie => {
|
||||||
for (const attr of unsupportedCookieAttributes) delete cookie[attr];
|
for (const attr of unsupportedCookieAttributes) {
|
||||||
|
delete (cookie as unknown as Record<string, unknown>)[attr];
|
||||||
|
}
|
||||||
return cookie;
|
return cookie;
|
||||||
};
|
};
|
||||||
return originalCookies.map(filterUnsupportedAttributes);
|
return originalCookies.map(filterUnsupportedAttributes);
|
||||||
@ -1505,9 +1511,14 @@ export class Page extends EventEmitter {
|
|||||||
private _buildMetricsObject(
|
private _buildMetricsObject(
|
||||||
metrics?: Protocol.Performance.Metric[]
|
metrics?: Protocol.Performance.Metric[]
|
||||||
): Metrics {
|
): Metrics {
|
||||||
const result = {};
|
const result: Record<
|
||||||
|
Protocol.Performance.Metric['name'],
|
||||||
|
Protocol.Performance.Metric['value']
|
||||||
|
> = {};
|
||||||
for (const metric of metrics || []) {
|
for (const metric of metrics || []) {
|
||||||
if (supportedMetrics.has(metric.name)) result[metric.name] = metric.value;
|
if (supportedMetrics.has(metric.name)) {
|
||||||
|
result[metric.name] = metric.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -1563,7 +1574,9 @@ export class Page extends EventEmitter {
|
|||||||
if (type !== 'exposedFun' || !this._pageBindings.has(name)) return;
|
if (type !== 'exposedFun' || !this._pageBindings.has(name)) return;
|
||||||
let expression = null;
|
let expression = null;
|
||||||
try {
|
try {
|
||||||
const result = await this._pageBindings.get(name)(...args);
|
const pageBinding = this._pageBindings.get(name);
|
||||||
|
assert(pageBinding);
|
||||||
|
const result = await pageBinding(...args);
|
||||||
expression = helper.pageBindingDeliverResultString(name, seq, result);
|
expression = helper.pageBindingDeliverResultString(name, seq, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error)
|
if (error instanceof Error)
|
||||||
@ -1589,7 +1602,7 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _addConsoleMessage(
|
private _addConsoleMessage(
|
||||||
type: ConsoleMessageType,
|
eventType: ConsoleMessageType,
|
||||||
args: JSHandle[],
|
args: JSHandle[],
|
||||||
stackTrace?: Protocol.Runtime.StackTrace
|
stackTrace?: Protocol.Runtime.StackTrace
|
||||||
): void {
|
): void {
|
||||||
@ -1614,7 +1627,7 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const message = new ConsoleMessage(
|
const message = new ConsoleMessage(
|
||||||
type,
|
eventType,
|
||||||
textTokens.join(' '),
|
textTokens.join(' '),
|
||||||
args,
|
args,
|
||||||
stackTraceLocations
|
stackTraceLocations
|
||||||
@ -1764,7 +1777,7 @@ export class Page extends EventEmitter {
|
|||||||
async goto(
|
async goto(
|
||||||
url: string,
|
url: string,
|
||||||
options: WaitForOptions & { referer?: string } = {}
|
options: WaitForOptions & { referer?: string } = {}
|
||||||
): Promise<HTTPResponse> {
|
): Promise<HTTPResponse | null> {
|
||||||
return await this._frameManager.mainFrame().goto(url, options);
|
return await this._frameManager.mainFrame().goto(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1949,17 +1962,17 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
const networkManager = this._frameManager.networkManager();
|
const networkManager = this._frameManager.networkManager();
|
||||||
|
|
||||||
let idleResolveCallback;
|
let idleResolveCallback: () => void;
|
||||||
const idlePromise = new Promise((resolve) => {
|
const idlePromise = new Promise<void>((resolve) => {
|
||||||
idleResolveCallback = resolve;
|
idleResolveCallback = resolve;
|
||||||
});
|
});
|
||||||
|
|
||||||
let abortRejectCallback;
|
let abortRejectCallback: (error: Error) => void;
|
||||||
const abortPromise = new Promise<Error>((_, reject) => {
|
const abortPromise = new Promise<Error>((_, reject) => {
|
||||||
abortRejectCallback = reject;
|
abortRejectCallback = reject;
|
||||||
});
|
});
|
||||||
|
|
||||||
let idleTimer;
|
let idleTimer: NodeJS.Timeout;
|
||||||
const onIdle = () => idleResolveCallback();
|
const onIdle = () => idleResolveCallback();
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
@ -1980,7 +1993,7 @@ export class Page extends EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listenToEvent = (event) =>
|
const listenToEvent = (event: symbol) =>
|
||||||
helper.waitForEvent(
|
helper.waitForEvent(
|
||||||
networkManager,
|
networkManager,
|
||||||
event,
|
event,
|
||||||
@ -2033,15 +2046,21 @@ export class Page extends EventEmitter {
|
|||||||
): Promise<Frame> {
|
): Promise<Frame> {
|
||||||
const { timeout = this._timeoutSettings.timeout() } = options;
|
const { timeout = this._timeoutSettings.timeout() } = options;
|
||||||
|
|
||||||
async function predicate(frame: Frame) {
|
let predicate: (frame: Frame) => Promise<boolean>;
|
||||||
if (helper.isString(urlOrPredicate))
|
if (helper.isString(urlOrPredicate)) {
|
||||||
return urlOrPredicate === frame.url();
|
predicate = (frame: Frame) =>
|
||||||
if (typeof urlOrPredicate === 'function')
|
Promise.resolve(urlOrPredicate === frame.url());
|
||||||
return !!(await urlOrPredicate(frame));
|
} else {
|
||||||
return false;
|
predicate = (frame: Frame) => {
|
||||||
|
const value = urlOrPredicate(frame);
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventRace = Promise.race([
|
const eventRace: Promise<Frame> = Promise.race([
|
||||||
helper.waitForEvent(
|
helper.waitForEvent(
|
||||||
this._frameManager,
|
this._frameManager,
|
||||||
FrameManagerEmittedEvents.FrameAttached,
|
FrameManagerEmittedEvents.FrameAttached,
|
||||||
@ -2056,19 +2075,15 @@ export class Page extends EventEmitter {
|
|||||||
timeout,
|
timeout,
|
||||||
this._sessionClosePromise()
|
this._sessionClosePromise()
|
||||||
),
|
),
|
||||||
|
...this.frames().map(async (frame) => {
|
||||||
|
if (await predicate(frame)) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
return await eventRace;
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Promise.race([
|
return eventRace;
|
||||||
eventRace,
|
|
||||||
(async () => {
|
|
||||||
for (const frame of this.frames()) {
|
|
||||||
if (await predicate(frame)) {
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await eventRace;
|
|
||||||
})(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2317,10 +2332,9 @@ export class Page extends EventEmitter {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async emulateMediaFeatures(features?: MediaFeature[]): Promise<void> {
|
async emulateMediaFeatures(features?: MediaFeature[]): Promise<void> {
|
||||||
if (features === null)
|
if (!features) await this._client.send('Emulation.setEmulatedMedia', {});
|
||||||
await this._client.send('Emulation.setEmulatedMedia', { features: null });
|
|
||||||
if (Array.isArray(features)) {
|
if (Array.isArray(features)) {
|
||||||
features.every((mediaFeature) => {
|
for (const mediaFeature of features) {
|
||||||
const name = mediaFeature.name;
|
const name = mediaFeature.name;
|
||||||
assert(
|
assert(
|
||||||
/^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.test(
|
/^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.test(
|
||||||
@ -2328,8 +2342,7 @@ export class Page extends EventEmitter {
|
|||||||
),
|
),
|
||||||
'Unsupported media feature: ' + name
|
'Unsupported media feature: ' + name
|
||||||
);
|
);
|
||||||
return true;
|
}
|
||||||
});
|
|
||||||
await this._client.send('Emulation.setEmulatedMedia', {
|
await this._client.send('Emulation.setEmulatedMedia', {
|
||||||
features: features,
|
features: features,
|
||||||
});
|
});
|
||||||
@ -2348,7 +2361,7 @@ export class Page extends EventEmitter {
|
|||||||
timezoneId: timezoneId || '',
|
timezoneId: timezoneId || '',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.includes('Invalid timezone'))
|
if (error instanceof Error && error.message.includes('Invalid timezone'))
|
||||||
throw new Error(`Invalid timezone ID: ${timezoneId}`);
|
throw new Error(`Invalid timezone ID: ${timezoneId}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -2652,7 +2665,7 @@ export class Page extends EventEmitter {
|
|||||||
* the value of `encoding`) with captured screenshot.
|
* the value of `encoding`) with captured screenshot.
|
||||||
*/
|
*/
|
||||||
async screenshot(options: ScreenshotOptions = {}): Promise<Buffer | string> {
|
async screenshot(options: ScreenshotOptions = {}): Promise<Buffer | string> {
|
||||||
let screenshotType = null;
|
let screenshotType = Protocol.Page.CaptureScreenshotRequestFormat.Png;
|
||||||
// options.type takes precedence over inferring the type from options.path
|
// options.type takes precedence over inferring the type from options.path
|
||||||
// because it may be a 0-length file with no extension created beforehand
|
// because it may be a 0-length file with no extension created beforehand
|
||||||
// (i.e. as a temp file).
|
// (i.e. as a temp file).
|
||||||
@ -2661,27 +2674,35 @@ export class Page extends EventEmitter {
|
|||||||
if (type !== 'png' && type !== 'jpeg' && type !== 'webp') {
|
if (type !== 'png' && type !== 'jpeg' && type !== 'webp') {
|
||||||
assertNever(type, 'Unknown options.type value: ' + type);
|
assertNever(type, 'Unknown options.type value: ' + type);
|
||||||
}
|
}
|
||||||
screenshotType = options.type;
|
screenshotType =
|
||||||
|
options.type as Protocol.Page.CaptureScreenshotRequestFormat;
|
||||||
} else if (options.path) {
|
} else if (options.path) {
|
||||||
const filePath = options.path;
|
const filePath = options.path;
|
||||||
const extension = filePath
|
const extension = filePath
|
||||||
.slice(filePath.lastIndexOf('.') + 1)
|
.slice(filePath.lastIndexOf('.') + 1)
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if (extension === 'png') screenshotType = 'png';
|
switch (extension) {
|
||||||
else if (extension === 'jpg' || extension === 'jpeg')
|
case 'png':
|
||||||
screenshotType = 'jpeg';
|
screenshotType = Protocol.Page.CaptureScreenshotRequestFormat.Png;
|
||||||
else if (extension === 'webp') screenshotType = 'webp';
|
break;
|
||||||
assert(
|
case 'jpeg':
|
||||||
screenshotType,
|
case 'jpg':
|
||||||
`Unsupported screenshot type for extension \`.${extension}\``
|
screenshotType = Protocol.Page.CaptureScreenshotRequestFormat.Jpeg;
|
||||||
);
|
break;
|
||||||
|
case 'webp':
|
||||||
|
screenshotType = Protocol.Page.CaptureScreenshotRequestFormat.Webp;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported screenshot type for extension \`.${extension}\``
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!screenshotType) screenshotType = 'png';
|
|
||||||
|
|
||||||
if (options.quality) {
|
if (options.quality) {
|
||||||
assert(
|
assert(
|
||||||
screenshotType === 'jpeg' || screenshotType === 'webp',
|
screenshotType === Protocol.Page.CaptureScreenshotRequestFormat.Jpeg ||
|
||||||
|
screenshotType === Protocol.Page.CaptureScreenshotRequestFormat.Webp,
|
||||||
'options.quality is unsupported for the ' +
|
'options.quality is unsupported for the ' +
|
||||||
screenshotType +
|
screenshotType +
|
||||||
' screenshots'
|
' screenshots'
|
||||||
@ -2742,7 +2763,7 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
private async _screenshotTask(
|
private async _screenshotTask(
|
||||||
format: Protocol.Page.CaptureScreenshotRequestFormat,
|
format: Protocol.Page.CaptureScreenshotRequestFormat,
|
||||||
options?: ScreenshotOptions
|
options: ScreenshotOptions = {}
|
||||||
): Promise<Buffer | string> {
|
): Promise<Buffer | string> {
|
||||||
await this._client.send('Target.activateTarget', {
|
await this._client.send('Target.activateTarget', {
|
||||||
targetId: this._target._targetId,
|
targetId: this._target._targetId,
|
||||||
@ -2861,7 +2882,8 @@ export class Page extends EventEmitter {
|
|||||||
let paperWidth = 8.5;
|
let paperWidth = 8.5;
|
||||||
let paperHeight = 11;
|
let paperHeight = 11;
|
||||||
if (options.format) {
|
if (options.format) {
|
||||||
const format = paperFormats[options.format.toLowerCase()];
|
const format =
|
||||||
|
paperFormats[options.format.toLowerCase() as LowerCasePaperFormat];
|
||||||
assert(format, 'Unknown paper format: ' + options.format);
|
assert(format, 'Unknown paper format: ' + options.format);
|
||||||
paperWidth = format.width;
|
paperWidth = format.width;
|
||||||
paperHeight = format.height;
|
paperHeight = format.height;
|
||||||
@ -2908,6 +2930,7 @@ export class Page extends EventEmitter {
|
|||||||
await this._resetDefaultBackgroundColor();
|
await this._resetDefaultBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(result.stream, '`stream` is missing from `Page.printToPDF');
|
||||||
return helper.getReadableFromProtocolStream(this._client, result.stream);
|
return helper.getReadableFromProtocolStream(this._client, result.stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2918,7 +2941,9 @@ export class Page extends EventEmitter {
|
|||||||
async pdf(options: PDFOptions = {}): Promise<Buffer> {
|
async pdf(options: PDFOptions = {}): Promise<Buffer> {
|
||||||
const { path = undefined } = options;
|
const { path = undefined } = options;
|
||||||
const readable = await this.createPDFStream(options);
|
const readable = await this.createPDFStream(options);
|
||||||
return await helper.getReadableAsBuffer(readable, path);
|
const buffer = await helper.getReadableAsBuffer(readable, path);
|
||||||
|
assert(buffer, 'Could not create buffer');
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3134,7 +3159,7 @@ export class Page extends EventEmitter {
|
|||||||
polling?: string | number;
|
polling?: string | number;
|
||||||
} = {},
|
} = {},
|
||||||
...args: SerializableOrJSHandle[]
|
...args: SerializableOrJSHandle[]
|
||||||
): Promise<JSHandle> {
|
): Promise<JSHandle | null> {
|
||||||
return this.mainFrame().waitFor(
|
return this.mainFrame().waitFor(
|
||||||
selectorOrFunctionOrTimeout,
|
selectorOrFunctionOrTimeout,
|
||||||
options,
|
options,
|
||||||
@ -3396,7 +3421,7 @@ function convertPrintParameterToInches(
|
|||||||
const text = /** @type {string} */ parameter;
|
const text = /** @type {string} */ parameter;
|
||||||
let unit = text.substring(text.length - 2).toLowerCase();
|
let unit = text.substring(text.length - 2).toLowerCase();
|
||||||
let valueText = '';
|
let valueText = '';
|
||||||
if (unitToPixels.hasOwnProperty(unit)) {
|
if (unit in unitToPixels) {
|
||||||
valueText = text.substring(0, text.length - 2);
|
valueText = text.substring(0, text.length - 2);
|
||||||
} else {
|
} else {
|
||||||
// In case of unknown unit try to parse the whole parameter as number of pixels.
|
// In case of unknown unit try to parse the whole parameter as number of pixels.
|
||||||
@ -3406,7 +3431,7 @@ function convertPrintParameterToInches(
|
|||||||
}
|
}
|
||||||
const value = Number(valueText);
|
const value = Number(valueText);
|
||||||
assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
|
assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
|
||||||
pixels = value * unitToPixels[unit];
|
pixels = value * unitToPixels[unit as keyof typeof unitToPixels];
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'page.pdf() Cannot handle parameter type: ' + typeof parameter
|
'page.pdf() Cannot handle parameter type: ' + typeof parameter
|
||||||
|
@ -38,7 +38,7 @@ export interface InternalQueryHandler {
|
|||||||
queryAllArray?: (
|
queryAllArray?: (
|
||||||
element: ElementHandle,
|
element: ElementHandle,
|
||||||
selector: string
|
selector: string
|
||||||
) => Promise<JSHandle>;
|
) => Promise<JSHandle<Element[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,8 +64,9 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
const internalHandler: InternalQueryHandler = {};
|
const internalHandler: InternalQueryHandler = {};
|
||||||
|
|
||||||
if (handler.queryOne) {
|
if (handler.queryOne) {
|
||||||
|
const queryOne = handler.queryOne;
|
||||||
internalHandler.queryOne = async (element, selector) => {
|
internalHandler.queryOne = async (element, selector) => {
|
||||||
const jsHandle = await element.evaluateHandle(handler.queryOne, selector);
|
const jsHandle = await element.evaluateHandle(queryOne, selector);
|
||||||
const elementHandle = jsHandle.asElement();
|
const elementHandle = jsHandle.asElement();
|
||||||
if (elementHandle) return elementHandle;
|
if (elementHandle) return elementHandle;
|
||||||
await jsHandle.dispose();
|
await jsHandle.dispose();
|
||||||
@ -75,12 +76,13 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
domWorld: DOMWorld,
|
domWorld: DOMWorld,
|
||||||
selector: string,
|
selector: string,
|
||||||
options: WaitForSelectorOptions
|
options: WaitForSelectorOptions
|
||||||
) => domWorld.waitForSelectorInPage(handler.queryOne, selector, options);
|
) => domWorld.waitForSelectorInPage(queryOne, selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler.queryAll) {
|
if (handler.queryAll) {
|
||||||
|
const queryAll = handler.queryAll;
|
||||||
internalHandler.queryAll = async (element, selector) => {
|
internalHandler.queryAll = async (element, selector) => {
|
||||||
const jsHandle = await element.evaluateHandle(handler.queryAll, selector);
|
const jsHandle = await element.evaluateHandle(queryAll, selector);
|
||||||
const properties = await jsHandle.getProperties();
|
const properties = await jsHandle.getProperties();
|
||||||
await jsHandle.dispose();
|
await jsHandle.dispose();
|
||||||
const result = [];
|
const result = [];
|
||||||
@ -91,10 +93,7 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
internalHandler.queryAllArray = async (element, selector) => {
|
internalHandler.queryAllArray = async (element, selector) => {
|
||||||
const resultHandle = await element.evaluateHandle(
|
const resultHandle = await element.evaluateHandle(queryAll, selector);
|
||||||
handler.queryAll,
|
|
||||||
selector
|
|
||||||
);
|
|
||||||
const arrayHandle = await resultHandle.evaluateHandle(
|
const arrayHandle = await resultHandle.evaluateHandle(
|
||||||
(res: Element[] | NodeListOf<Element>) => Array.from(res)
|
(res: Element[] | NodeListOf<Element>) => Array.from(res)
|
||||||
);
|
);
|
||||||
@ -106,9 +105,9 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _defaultHandler = makeQueryHandler({
|
const _defaultHandler = makeQueryHandler({
|
||||||
queryOne: (element: Element, selector: string) =>
|
queryOne: (element: Element | Document, selector: string) =>
|
||||||
element.querySelector(selector),
|
element.querySelector(selector),
|
||||||
queryAll: (element: Element, selector: string) =>
|
queryAll: (element: Element | Document, selector: string) =>
|
||||||
element.querySelectorAll(selector),
|
element.querySelectorAll(selector),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ export class Target {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_initializedCallback: (x: boolean) => void;
|
_initializedCallback!: (x: boolean) => void;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -50,7 +50,7 @@ export class Target {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_closedCallback: () => void;
|
_closedCallback!: () => void;
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -81,13 +81,9 @@ export class Target {
|
|||||||
this._targetId = targetInfo.targetId;
|
this._targetId = targetInfo.targetId;
|
||||||
this._sessionFactory = sessionFactory;
|
this._sessionFactory = sessionFactory;
|
||||||
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
|
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||||
this._defaultViewport = defaultViewport;
|
this._defaultViewport = defaultViewport ?? undefined;
|
||||||
this._screenshotTaskQueue = screenshotTaskQueue;
|
this._screenshotTaskQueue = screenshotTaskQueue;
|
||||||
this._isPageTargetCallback = isPageTargetCallback;
|
this._isPageTargetCallback = isPageTargetCallback;
|
||||||
/** @type {?Promise<!Puppeteer.Page>} */
|
|
||||||
this._pagePromise = null;
|
|
||||||
/** @type {?Promise<!WebWorker>} */
|
|
||||||
this._workerPromise = null;
|
|
||||||
this._initializedPromise = new Promise<boolean>(
|
this._initializedPromise = new Promise<boolean>(
|
||||||
(fulfill) => (this._initializedCallback = fulfill)
|
(fulfill) => (this._initializedCallback = fulfill)
|
||||||
).then(async (success) => {
|
).then(async (success) => {
|
||||||
@ -120,14 +116,14 @@ export class Target {
|
|||||||
/**
|
/**
|
||||||
* If the target is not of type `"page"` or `"background_page"`, returns `null`.
|
* If the target is not of type `"page"` or `"background_page"`, returns `null`.
|
||||||
*/
|
*/
|
||||||
async page(): Promise<Page | null> {
|
async page(): Promise<Page | undefined> {
|
||||||
if (this._isPageTargetCallback(this._targetInfo) && !this._pagePromise) {
|
if (this._isPageTargetCallback(this._targetInfo) && !this._pagePromise) {
|
||||||
this._pagePromise = this._sessionFactory().then((client) =>
|
this._pagePromise = this._sessionFactory().then((client) =>
|
||||||
Page.create(
|
Page.create(
|
||||||
client,
|
client,
|
||||||
this,
|
this,
|
||||||
this._ignoreHTTPSErrors,
|
this._ignoreHTTPSErrors,
|
||||||
this._defaultViewport,
|
this._defaultViewport ?? null,
|
||||||
this._screenshotTaskQueue
|
this._screenshotTaskQueue
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -208,9 +204,9 @@ export class Target {
|
|||||||
/**
|
/**
|
||||||
* Get the target that opened this target. Top-level targets return `null`.
|
* Get the target that opened this target. Top-level targets return `null`.
|
||||||
*/
|
*/
|
||||||
opener(): Target | null {
|
opener(): Target | undefined {
|
||||||
const { openerId } = this._targetInfo;
|
const { openerId } = this._targetInfo;
|
||||||
if (!openerId) return null;
|
if (!openerId) return;
|
||||||
return this.browser()._targets.get(openerId);
|
return this.browser()._targets.get(openerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export interface TracingOptions {
|
|||||||
export class Tracing {
|
export class Tracing {
|
||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
_recording = false;
|
_recording = false;
|
||||||
_path = '';
|
_path?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -79,7 +79,7 @@ export class Tracing {
|
|||||||
'disabled-by-default-v8.cpu_profiler',
|
'disabled-by-default-v8.cpu_profiler',
|
||||||
];
|
];
|
||||||
const {
|
const {
|
||||||
path = null,
|
path,
|
||||||
screenshots = false,
|
screenshots = false,
|
||||||
categories = defaultCategories,
|
categories = defaultCategories,
|
||||||
} = options;
|
} = options;
|
||||||
@ -106,11 +106,11 @@ export class Tracing {
|
|||||||
* Stops a trace started with the `start` method.
|
* Stops a trace started with the `start` method.
|
||||||
* @returns Promise which resolves to buffer with trace data.
|
* @returns Promise which resolves to buffer with trace data.
|
||||||
*/
|
*/
|
||||||
async stop(): Promise<Buffer> {
|
async stop(): Promise<Buffer | undefined> {
|
||||||
let fulfill: (value: Buffer) => void;
|
let resolve: (value: Buffer | undefined) => void;
|
||||||
let reject: (err: Error) => void;
|
let reject: (err: Error) => void;
|
||||||
const contentPromise = new Promise<Buffer>((x, y) => {
|
const contentPromise = new Promise<Buffer | undefined>((x, y) => {
|
||||||
fulfill = x;
|
resolve = x;
|
||||||
reject = y;
|
reject = y;
|
||||||
});
|
});
|
||||||
this._client.once('Tracing.tracingComplete', async (event) => {
|
this._client.once('Tracing.tracingComplete', async (event) => {
|
||||||
@ -120,9 +120,13 @@ export class Tracing {
|
|||||||
event.stream
|
event.stream
|
||||||
);
|
);
|
||||||
const buffer = await helper.getReadableAsBuffer(readable, this._path);
|
const buffer = await helper.getReadableAsBuffer(readable, this._path);
|
||||||
fulfill(buffer);
|
resolve(buffer ?? undefined);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
if (error instanceof Error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Unknown error: ${error}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this._client.send('Tracing.end');
|
await this._client.send('Tracing.end');
|
||||||
|
@ -20,12 +20,13 @@ import { JSHandle } from './JSHandle.js';
|
|||||||
import { CDPSession } from './Connection.js';
|
import { CDPSession } from './Connection.js';
|
||||||
import { Protocol } from 'devtools-protocol';
|
import { Protocol } from 'devtools-protocol';
|
||||||
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes.js';
|
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes.js';
|
||||||
|
import { ConsoleMessageType } from './ConsoleMessage.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export type ConsoleAPICalledCallback = (
|
export type ConsoleAPICalledCallback = (
|
||||||
eventType: string,
|
eventType: ConsoleMessageType,
|
||||||
handles: JSHandle[],
|
handles: JSHandle[],
|
||||||
trace: Protocol.Runtime.StackTrace
|
trace: Protocol.Runtime.StackTrace
|
||||||
) => void;
|
) => void;
|
||||||
@ -63,7 +64,7 @@ export class WebWorker extends EventEmitter {
|
|||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
_url: string;
|
_url: string;
|
||||||
_executionContextPromise: Promise<ExecutionContext>;
|
_executionContextPromise: Promise<ExecutionContext>;
|
||||||
_executionContextCallback: (value: ExecutionContext) => void;
|
_executionContextCallback!: (value: ExecutionContext) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -87,11 +88,7 @@ export class WebWorker extends EventEmitter {
|
|||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
jsHandleFactory = (remoteObject) =>
|
jsHandleFactory = (remoteObject) =>
|
||||||
new JSHandle(executionContext, client, remoteObject);
|
new JSHandle(executionContext, client, remoteObject);
|
||||||
const executionContext = new ExecutionContext(
|
const executionContext = new ExecutionContext(client, event.context);
|
||||||
client,
|
|
||||||
event.context,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
this._executionContextCallback(executionContext);
|
this._executionContextCallback(executionContext);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,10 +19,16 @@
|
|||||||
* @param value
|
* @param value
|
||||||
* @param message - the error message to throw if the value is not truthy.
|
* @param message - the error message to throw if the value is not truthy.
|
||||||
*/
|
*/
|
||||||
export const assert = (value: unknown, message?: string): void => {
|
export const assert: (value: unknown, message?: string) => asserts value = (
|
||||||
|
value,
|
||||||
|
message
|
||||||
|
) => {
|
||||||
if (!value) throw new Error(message);
|
if (!value) throw new Error(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const assertNever = (value: never, message?: string): void => {
|
export const assertNever: (
|
||||||
|
value: unknown,
|
||||||
|
message?: string
|
||||||
|
) => asserts value is never = (value, message) => {
|
||||||
if (value) throw new Error(message);
|
if (value) throw new Error(message);
|
||||||
};
|
};
|
||||||
|
@ -228,13 +228,13 @@ function pageBindingDeliverErrorString(
|
|||||||
name: string,
|
name: string,
|
||||||
seq: number,
|
seq: number,
|
||||||
message: string,
|
message: string,
|
||||||
stack: string
|
stack?: string
|
||||||
): string {
|
): string {
|
||||||
function deliverError(
|
function deliverError(
|
||||||
name: string,
|
name: string,
|
||||||
seq: number,
|
seq: number,
|
||||||
message: string,
|
message: string,
|
||||||
stack: string
|
stack?: string
|
||||||
): void {
|
): void {
|
||||||
const error = new Error(message);
|
const error = new Error(message);
|
||||||
error.stack = stack;
|
error.stack = stack;
|
||||||
@ -304,7 +304,7 @@ async function waitWithTimeout<T>(
|
|||||||
const timeoutError = new TimeoutError(
|
const timeoutError = new TimeoutError(
|
||||||
`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`
|
`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`
|
||||||
);
|
);
|
||||||
const timeoutPromise = new Promise<T>((resolve, x) => (reject = x));
|
const timeoutPromise = new Promise<T>((_res, rej) => (reject = rej));
|
||||||
let timeoutTimer = null;
|
let timeoutTimer = null;
|
||||||
if (timeout) timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
if (timeout) timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
||||||
try {
|
try {
|
||||||
@ -362,7 +362,7 @@ async function getReadableFromProtocolStream(
|
|||||||
return new Readable({
|
return new Readable({
|
||||||
async read(size: number) {
|
async read(size: number) {
|
||||||
if (eof) {
|
if (eof) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await client.send('IO.read', { handle, size });
|
const response = await client.send('IO.read', { handle, size });
|
||||||
|
@ -27,9 +27,9 @@ export const initializePuppeteerNode = (packageName: string): PuppeteerNode => {
|
|||||||
// puppeteer-core ignores environment variables
|
// puppeteer-core ignores environment variables
|
||||||
const productName = isPuppeteerCore
|
const productName = isPuppeteerCore
|
||||||
? undefined
|
? undefined
|
||||||
: process.env.PUPPETEER_PRODUCT ||
|
: process.env['PUPPETEER_PRODUCT'] ||
|
||||||
process.env.npm_config_puppeteer_product ||
|
process.env['npm_config_puppeteer_product'] ||
|
||||||
process.env.npm_package_config_puppeteer_product;
|
process.env['npm_package_config_puppeteer_product'];
|
||||||
|
|
||||||
if (!isPuppeteerCore && productName === 'firefox')
|
if (!isPuppeteerCore && productName === 'firefox')
|
||||||
preferredRevision = PUPPETEER_REVISIONS.firefox;
|
preferredRevision = PUPPETEER_REVISIONS.firefox;
|
||||||
|
@ -42,7 +42,7 @@ const { PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM } = process.env;
|
|||||||
|
|
||||||
const debugFetcher = debug('puppeteer:fetcher');
|
const debugFetcher = debug('puppeteer:fetcher');
|
||||||
|
|
||||||
const downloadURLs = {
|
const downloadURLs: Record<Product, Partial<Record<Platform, string>>> = {
|
||||||
chrome: {
|
chrome: {
|
||||||
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
|
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
|
||||||
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
|
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
|
||||||
@ -56,7 +56,7 @@ const downloadURLs = {
|
|||||||
win32: '%s/firefox-%s.en-US.%s.zip',
|
win32: '%s/firefox-%s.en-US.%s.zip',
|
||||||
win64: '%s/firefox-%s.en-US.%s.zip',
|
win64: '%s/firefox-%s.en-US.%s.zip',
|
||||||
},
|
},
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
const browserConfig = {
|
const browserConfig = {
|
||||||
chrome: {
|
chrome: {
|
||||||
@ -80,15 +80,23 @@ function archiveName(
|
|||||||
platform: Platform,
|
platform: Platform,
|
||||||
revision: string
|
revision: string
|
||||||
): string {
|
): string {
|
||||||
if (product === 'chrome') {
|
switch (product) {
|
||||||
if (platform === 'linux') return 'chrome-linux';
|
case 'chrome':
|
||||||
if (platform === 'mac' || platform === 'mac_arm') return 'chrome-mac';
|
switch (platform) {
|
||||||
if (platform === 'win32' || platform === 'win64') {
|
case 'linux':
|
||||||
// Windows archive name changed at r591479.
|
return 'chrome-linux';
|
||||||
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
|
case 'mac_arm':
|
||||||
}
|
case 'mac':
|
||||||
} else if (product === 'firefox') {
|
return 'chrome-mac';
|
||||||
return platform;
|
case 'win32':
|
||||||
|
case 'win64':
|
||||||
|
// Windows archive name changed at r591479.
|
||||||
|
return parseInt(revision, 10) > 591479
|
||||||
|
? 'chrome-win'
|
||||||
|
: 'chrome-win32';
|
||||||
|
}
|
||||||
|
case 'firefox':
|
||||||
|
return platform;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,9 +122,9 @@ function downloadURL(
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
function handleArm64(): void {
|
function handleArm64(): void {
|
||||||
fs.stat('/usr/bin/chromium-browser', function (err, stats) {
|
fs.stat('/usr/bin/chromium-browser', function (_err, stats) {
|
||||||
if (stats === undefined) {
|
if (stats === undefined) {
|
||||||
fs.stat('/usr/bin/chromium', function (err, stats) {
|
fs.stat('/usr/bin/chromium', function (_err, stats) {
|
||||||
if (stats === undefined) {
|
if (stats === undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
'The chromium binary is not available for arm64.' +
|
'The chromium binary is not available for arm64.' +
|
||||||
@ -206,38 +214,42 @@ export class BrowserFetcher {
|
|||||||
options.path ||
|
options.path ||
|
||||||
path.join(projectRoot, browserConfig[this._product].destination);
|
path.join(projectRoot, browserConfig[this._product].destination);
|
||||||
this._downloadHost = options.host || browserConfig[this._product].host;
|
this._downloadHost = options.host || browserConfig[this._product].host;
|
||||||
this.setPlatform(options.platform, this._product);
|
|
||||||
|
if (options.platform) {
|
||||||
|
this._platform = options.platform;
|
||||||
|
} else {
|
||||||
|
const platform = os.platform();
|
||||||
|
switch (platform) {
|
||||||
|
case 'darwin':
|
||||||
|
switch (this._product) {
|
||||||
|
case 'chrome':
|
||||||
|
this._platform =
|
||||||
|
os.arch() === 'arm64' && PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM
|
||||||
|
? 'mac_arm'
|
||||||
|
: 'mac';
|
||||||
|
break;
|
||||||
|
case 'firefox':
|
||||||
|
this._platform = 'mac';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'linux':
|
||||||
|
this._platform = 'linux';
|
||||||
|
break;
|
||||||
|
case 'win32':
|
||||||
|
this._platform = os.arch() === 'x64' ? 'win64' : 'win32';
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
assert(false, 'Unsupported platform: ' + platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
downloadURLs[this._product][this._platform],
|
downloadURLs[this._product][this._platform],
|
||||||
'Unsupported platform: ' + this._platform
|
'Unsupported platform: ' + this._platform
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPlatform(
|
|
||||||
platformFromOptions?: Platform,
|
|
||||||
productFromOptions?: Product
|
|
||||||
): void {
|
|
||||||
if (platformFromOptions) {
|
|
||||||
this._platform = platformFromOptions;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const platform = os.platform();
|
|
||||||
if (platform === 'darwin') {
|
|
||||||
if (productFromOptions === 'chrome') {
|
|
||||||
this._platform =
|
|
||||||
os.arch() === 'arm64' && PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM
|
|
||||||
? 'mac_arm'
|
|
||||||
: 'mac';
|
|
||||||
} else if (productFromOptions === 'firefox') {
|
|
||||||
this._platform = 'mac';
|
|
||||||
}
|
|
||||||
} else if (platform === 'linux') this._platform = 'linux';
|
|
||||||
else if (platform === 'win32')
|
|
||||||
this._platform = os.arch() === 'x64' ? 'win64' : 'win32';
|
|
||||||
else assert(this._platform, 'Unsupported platform: ' + platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns Returns the current `Platform`, which is one of `mac`, `linux`,
|
* @returns Returns the current `Platform`, which is one of `mac`, `linux`,
|
||||||
* `win32` or `win64`.
|
* `win32` or `win64`.
|
||||||
@ -305,7 +317,7 @@ export class BrowserFetcher {
|
|||||||
async download(
|
async download(
|
||||||
revision: string,
|
revision: string,
|
||||||
progressCallback: (x: number, y: number) => void = (): void => {}
|
progressCallback: (x: number, y: number) => void = (): void => {}
|
||||||
): Promise<BrowserFetcherRevisionInfo> {
|
): Promise<BrowserFetcherRevisionInfo | undefined> {
|
||||||
const url = downloadURL(
|
const url = downloadURL(
|
||||||
this._product,
|
this._product,
|
||||||
this._platform,
|
this._platform,
|
||||||
@ -313,6 +325,7 @@ export class BrowserFetcher {
|
|||||||
revision
|
revision
|
||||||
);
|
);
|
||||||
const fileName = url.split('/').pop();
|
const fileName = url.split('/').pop();
|
||||||
|
assert(fileName, `A malformed download URL was found: ${url}.`);
|
||||||
const archivePath = path.join(this._downloadsFolder, fileName);
|
const archivePath = path.join(this._downloadsFolder, fileName);
|
||||||
const outputPath = this._getFolderPath(revision);
|
const outputPath = this._getFolderPath(revision);
|
||||||
if (await existsAsync(outputPath)) return this.revisionInfo(revision);
|
if (await existsAsync(outputPath)) return this.revisionInfo(revision);
|
||||||
@ -346,7 +359,12 @@ export class BrowserFetcher {
|
|||||||
const fileNames = await readdirAsync(this._downloadsFolder);
|
const fileNames = await readdirAsync(this._downloadsFolder);
|
||||||
return fileNames
|
return fileNames
|
||||||
.map((fileName) => parseFolderPath(this._product, fileName))
|
.map((fileName) => parseFolderPath(this._product, fileName))
|
||||||
.filter((entry) => entry && entry.platform === this._platform)
|
.filter(
|
||||||
|
(
|
||||||
|
entry
|
||||||
|
): entry is { product: string; platform: string; revision: string } =>
|
||||||
|
(entry && entry.platform === this._platform) ?? false
|
||||||
|
)
|
||||||
.map((entry) => entry.revision);
|
.map((entry) => entry.revision);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,12 +465,12 @@ export class BrowserFetcher {
|
|||||||
function parseFolderPath(
|
function parseFolderPath(
|
||||||
product: Product,
|
product: Product,
|
||||||
folderPath: string
|
folderPath: string
|
||||||
): { product: string; platform: string; revision: string } | null {
|
): { product: string; platform: string; revision: string } | undefined {
|
||||||
const name = path.basename(folderPath);
|
const name = path.basename(folderPath);
|
||||||
const splits = name.split('-');
|
const splits = name.split('-');
|
||||||
if (splits.length !== 2) return null;
|
if (splits.length !== 2) return;
|
||||||
const [platform, revision] = splits;
|
const [platform, revision] = splits;
|
||||||
if (!downloadURLs[product][platform]) return null;
|
if (!revision || !platform || !(platform in downloadURLs[product])) return;
|
||||||
return { product, platform, revision };
|
return { product, platform, revision };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,18 +480,19 @@ function parseFolderPath(
|
|||||||
function downloadFile(
|
function downloadFile(
|
||||||
url: string,
|
url: string,
|
||||||
destinationPath: string,
|
destinationPath: string,
|
||||||
progressCallback: (x: number, y: number) => void
|
progressCallback?: (x: number, y: number) => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
debugFetcher(`Downloading binary from ${url}`);
|
debugFetcher(`Downloading binary from ${url}`);
|
||||||
let fulfill, reject;
|
let fulfill: (value: void | PromiseLike<void>) => void;
|
||||||
let downloadedBytes = 0;
|
let reject: (err: Error) => void;
|
||||||
let totalBytes = 0;
|
|
||||||
|
|
||||||
const promise = new Promise<void>((x, y) => {
|
const promise = new Promise<void>((x, y) => {
|
||||||
fulfill = x;
|
fulfill = x;
|
||||||
reject = y;
|
reject = y;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let downloadedBytes = 0;
|
||||||
|
let totalBytes = 0;
|
||||||
|
|
||||||
const request = httpRequest(url, 'GET', (response) => {
|
const request = httpRequest(url, 'GET', (response) => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
@ -488,10 +507,7 @@ function downloadFile(
|
|||||||
file.on('finish', () => fulfill());
|
file.on('finish', () => fulfill());
|
||||||
file.on('error', (error) => reject(error));
|
file.on('error', (error) => reject(error));
|
||||||
response.pipe(file);
|
response.pipe(file);
|
||||||
totalBytes = parseInt(
|
totalBytes = parseInt(response.headers['content-length']!, 10);
|
||||||
/** @type {string} */ response.headers['content-length'],
|
|
||||||
10
|
|
||||||
);
|
|
||||||
if (progressCallback) response.on('data', onData);
|
if (progressCallback) response.on('data', onData);
|
||||||
});
|
});
|
||||||
request.on('error', (error) => reject(error));
|
request.on('error', (error) => reject(error));
|
||||||
@ -499,7 +515,7 @@ function downloadFile(
|
|||||||
|
|
||||||
function onData(chunk: string): void {
|
function onData(chunk: string): void {
|
||||||
downloadedBytes += chunk.length;
|
downloadedBytes += chunk.length;
|
||||||
progressCallback(downloadedBytes, totalBytes);
|
progressCallback!(downloadedBytes, totalBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,16 +549,16 @@ function extractTar(tarPath: string, folderPath: string): Promise<unknown> {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
function installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
function installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
||||||
let mountPath;
|
let mountPath: string | undefined;
|
||||||
|
|
||||||
function mountAndCopy(fulfill: () => void, reject: (Error) => void): void {
|
return new Promise<void>((fulfill, reject): void => {
|
||||||
const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`;
|
const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`;
|
||||||
childProcess.exec(mountCommand, (err, stdout) => {
|
childProcess.exec(mountCommand, (err, stdout) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
const volumes = stdout.match(/\/Volumes\/(.*)/m);
|
const volumes = stdout.match(/\/Volumes\/(.*)/m);
|
||||||
if (!volumes)
|
if (!volumes)
|
||||||
return reject(new Error(`Could not find volume path in ${stdout}`));
|
return reject(new Error(`Could not find volume path in ${stdout}`));
|
||||||
mountPath = volumes[0];
|
mountPath = volumes[0]!;
|
||||||
readdirAsync(mountPath)
|
readdirAsync(mountPath)
|
||||||
.then((fileNames) => {
|
.then((fileNames) => {
|
||||||
const appName = fileNames.find(
|
const appName = fileNames.find(
|
||||||
@ -550,7 +566,7 @@ function installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
|||||||
);
|
);
|
||||||
if (!appName)
|
if (!appName)
|
||||||
return reject(new Error(`Cannot find app in ${mountPath}`));
|
return reject(new Error(`Cannot find app in ${mountPath}`));
|
||||||
const copyPath = path.join(mountPath, appName);
|
const copyPath = path.join(mountPath!, appName);
|
||||||
debugFetcher(`Copying ${copyPath} to ${folderPath}`);
|
debugFetcher(`Copying ${copyPath} to ${folderPath}`);
|
||||||
childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => {
|
childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
@ -559,22 +575,18 @@ function installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
|||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
|
|
||||||
function unmount(): void {
|
|
||||||
if (!mountPath) return;
|
|
||||||
const unmountCommand = `hdiutil detach "${mountPath}" -quiet`;
|
|
||||||
debugFetcher(`Unmounting ${mountPath}`);
|
|
||||||
childProcess.exec(unmountCommand, (err) => {
|
|
||||||
if (err) console.error(`Error unmounting dmg: ${err}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<void>(mountAndCopy)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
})
|
})
|
||||||
.finally(unmount);
|
.finally((): void => {
|
||||||
|
if (!mountPath) return;
|
||||||
|
const unmountCommand = `hdiutil detach "${mountPath}" -quiet`;
|
||||||
|
debugFetcher(`Unmounting ${mountPath}`);
|
||||||
|
childProcess.exec(unmountCommand, (err) => {
|
||||||
|
if (err) console.error(`Error unmounting dmg: ${err}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function httpRequest(
|
function httpRequest(
|
||||||
@ -625,7 +637,12 @@ function httpRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestCallback = (res: http.IncomingMessage): void => {
|
const requestCallback = (res: http.IncomingMessage): void => {
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
|
if (
|
||||||
|
res.statusCode &&
|
||||||
|
res.statusCode >= 300 &&
|
||||||
|
res.statusCode < 400 &&
|
||||||
|
res.headers.location
|
||||||
|
)
|
||||||
httpRequest(res.headers.location, method, response);
|
httpRequest(res.headers.location, method, response);
|
||||||
else response(res);
|
else response(res);
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,11 @@ import removeFolder from 'rimraf';
|
|||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { assert } from '../common/assert.js';
|
import { assert } from '../common/assert.js';
|
||||||
import { helper, debugError } from '../common/helper.js';
|
import {
|
||||||
|
helper,
|
||||||
|
debugError,
|
||||||
|
PuppeteerEventListener,
|
||||||
|
} from '../common/helper.js';
|
||||||
import { LaunchOptions } from './LaunchOptions.js';
|
import { LaunchOptions } from './LaunchOptions.js';
|
||||||
import { Connection } from '../common/Connection.js';
|
import { Connection } from '../common/Connection.js';
|
||||||
import { NodeWebSocketTransport as WebSocketTransport } from '../node/NodeWebSocketTransport.js';
|
import { NodeWebSocketTransport as WebSocketTransport } from '../node/NodeWebSocketTransport.js';
|
||||||
@ -50,12 +54,12 @@ export class BrowserRunner {
|
|||||||
private _userDataDir: string;
|
private _userDataDir: string;
|
||||||
private _isTempUserDataDir?: boolean;
|
private _isTempUserDataDir?: boolean;
|
||||||
|
|
||||||
proc = null;
|
proc?: childProcess.ChildProcess;
|
||||||
connection = null;
|
connection?: Connection;
|
||||||
|
|
||||||
private _closed = true;
|
private _closed = true;
|
||||||
private _listeners = [];
|
private _listeners: PuppeteerEventListener[] = [];
|
||||||
private _processClosing: Promise<void>;
|
private _processClosing!: Promise<void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
product: Product,
|
product: Product,
|
||||||
@ -100,12 +104,12 @@ export class BrowserRunner {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (dumpio) {
|
if (dumpio) {
|
||||||
this.proc.stderr.pipe(process.stderr);
|
this.proc.stderr?.pipe(process.stderr);
|
||||||
this.proc.stdout.pipe(process.stdout);
|
this.proc.stdout?.pipe(process.stdout);
|
||||||
}
|
}
|
||||||
this._closed = false;
|
this._closed = false;
|
||||||
this._processClosing = new Promise((fulfill, reject) => {
|
this._processClosing = new Promise((fulfill, reject) => {
|
||||||
this.proc.once('exit', async () => {
|
this.proc!.once('exit', async () => {
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
// Cleanup as processes exit.
|
// Cleanup as processes exit.
|
||||||
if (this._isTempUserDataDir) {
|
if (this._isTempUserDataDir) {
|
||||||
@ -184,6 +188,7 @@ export class BrowserRunner {
|
|||||||
// is invalid), then the process does not get a pid assigned. A call to
|
// is invalid), then the process does not get a pid assigned. A call to
|
||||||
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
|
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
|
||||||
if (this.proc && this.proc.pid && pidExists(this.proc.pid)) {
|
if (this.proc && this.proc.pid && pidExists(this.proc.pid)) {
|
||||||
|
const proc = this.proc;
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
childProcess.exec(`taskkill /pid ${this.proc.pid} /T /F`, (error) => {
|
childProcess.exec(`taskkill /pid ${this.proc.pid} /T /F`, (error) => {
|
||||||
@ -191,7 +196,7 @@ export class BrowserRunner {
|
|||||||
// taskkill can fail to kill the process e.g. due to missing permissions.
|
// taskkill can fail to kill the process e.g. due to missing permissions.
|
||||||
// Let's kill the process via Node API. This delays killing of all child
|
// Let's kill the process via Node API. This delays killing of all child
|
||||||
// proccesses of `this.proc` until the main Node.js process dies.
|
// proccesses of `this.proc` until the main Node.js process dies.
|
||||||
this.proc.kill();
|
proc.kill();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -202,7 +207,9 @@ export class BrowserRunner {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${PROCESS_ERROR_EXPLANATION}\nError cause: ${error.stack}`
|
`${PROCESS_ERROR_EXPLANATION}\nError cause: ${
|
||||||
|
error instanceof Error ? error.stack : error
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,6 +232,8 @@ export class BrowserRunner {
|
|||||||
slowMo: number;
|
slowMo: number;
|
||||||
preferredRevision: string;
|
preferredRevision: string;
|
||||||
}): Promise<Connection> {
|
}): Promise<Connection> {
|
||||||
|
assert(this.proc, 'BrowserRunner not started.');
|
||||||
|
|
||||||
const { usePipe, timeout, slowMo, preferredRevision } = options;
|
const { usePipe, timeout, slowMo, preferredRevision } = options;
|
||||||
if (!usePipe) {
|
if (!usePipe) {
|
||||||
const browserWSEndpoint = await waitForWSEndpoint(
|
const browserWSEndpoint = await waitForWSEndpoint(
|
||||||
@ -253,9 +262,11 @@ function waitForWSEndpoint(
|
|||||||
timeout: number,
|
timeout: number,
|
||||||
preferredRevision: string
|
preferredRevision: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
assert(browserProcess.stderr, '`browserProcess` does not have stderr.');
|
||||||
|
const rl = readline.createInterface(browserProcess.stderr);
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const rl = readline.createInterface({ input: browserProcess.stderr });
|
|
||||||
let stderr = '';
|
|
||||||
const listeners = [
|
const listeners = [
|
||||||
helper.addEventListener(rl, 'line', onLine),
|
helper.addEventListener(rl, 'line', onLine),
|
||||||
helper.addEventListener(rl, 'close', () => onClose()),
|
helper.addEventListener(rl, 'close', () => onClose()),
|
||||||
@ -299,7 +310,8 @@ function waitForWSEndpoint(
|
|||||||
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
||||||
if (!match) return;
|
if (!match) return;
|
||||||
cleanup();
|
cleanup();
|
||||||
resolve(match[1]);
|
// The RegExp matches, so this will obviously exist.
|
||||||
|
resolve(match[1]!);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup(): void {
|
function cleanup(): void {
|
||||||
@ -313,10 +325,12 @@ function pidExists(pid: number): boolean {
|
|||||||
try {
|
try {
|
||||||
return process.kill(pid, 0);
|
return process.kill(pid, 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && error.code && error.code === 'ESRCH') {
|
if (error instanceof Error) {
|
||||||
return false;
|
const err = error as NodeJS.ErrnoException;
|
||||||
} else {
|
if (err.code && err.code === 'ESRCH') {
|
||||||
throw error;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ import {
|
|||||||
|
|
||||||
import { Product } from '../common/Product.js';
|
import { Product } from '../common/Product.js';
|
||||||
|
|
||||||
const tmpDir = () => process.env.PUPPETEER_TMP_DIR || os.tmpdir();
|
const tmpDir = () => process.env['PUPPETEER_TMP_DIR'] || os.tmpdir();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a launcher - a class that is able to create and launch a browser instance.
|
* Describes a launcher - a class that is able to create and launch a browser instance.
|
||||||
@ -112,31 +112,33 @@ class ChromeLauncher implements ProductLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let userDataDir;
|
|
||||||
let isTempUserDataDir = true;
|
let isTempUserDataDir = true;
|
||||||
|
|
||||||
// Check for the user data dir argument, which will always be set even
|
// Check for the user data dir argument, which will always be set even
|
||||||
// with a custom directory specified via the userDataDir option.
|
// with a custom directory specified via the userDataDir option.
|
||||||
const userDataDirIndex = chromeArguments.findIndex((arg) => {
|
let userDataDirIndex = chromeArguments.findIndex((arg) => {
|
||||||
return arg.startsWith('--user-data-dir');
|
return arg.startsWith('--user-data-dir');
|
||||||
});
|
});
|
||||||
|
if (userDataDirIndex < 0) {
|
||||||
if (userDataDirIndex !== -1) {
|
chromeArguments.push(
|
||||||
userDataDir = chromeArguments[userDataDirIndex].split('=')[1];
|
`--user-data-dir=${await mkdtempAsync(
|
||||||
isTempUserDataDir = false;
|
path.join(tmpDir(), 'puppeteer_dev_chrome_profile-')
|
||||||
} else {
|
)}`
|
||||||
userDataDir = await mkdtempAsync(
|
|
||||||
path.join(tmpDir(), 'puppeteer_dev_chrome_profile-')
|
|
||||||
);
|
);
|
||||||
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
userDataDirIndex = chromeArguments.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userDataDir = chromeArguments[userDataDirIndex]!.split('=', 2)[1];
|
||||||
|
assert(typeof userDataDir === 'string', '`--user-data-dir` is malformed');
|
||||||
|
|
||||||
|
isTempUserDataDir = false;
|
||||||
|
|
||||||
let chromeExecutable = executablePath;
|
let chromeExecutable = executablePath;
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
// executablePath is detected by channel, so it should not be specified by user.
|
// executablePath is detected by channel, so it should not be specified by user.
|
||||||
assert(
|
assert(
|
||||||
!executablePath,
|
typeof executablePath === 'string',
|
||||||
'`executablePath` must not be specified when `channel` is given.'
|
'`executablePath` must not be specified when `channel` is given.'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -238,7 +240,7 @@ class ChromeLauncher implements ProductLauncher {
|
|||||||
devtools = false,
|
devtools = false,
|
||||||
headless = !devtools,
|
headless = !devtools,
|
||||||
args = [],
|
args = [],
|
||||||
userDataDir = null,
|
userDataDir,
|
||||||
} = options;
|
} = options;
|
||||||
if (userDataDir)
|
if (userDataDir)
|
||||||
chromeArguments.push(`--user-data-dir=${path.resolve(userDataDir)}`);
|
chromeArguments.push(`--user-data-dir=${path.resolve(userDataDir)}`);
|
||||||
@ -331,7 +333,7 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
firefoxArguments.push(`--remote-debugging-port=${debuggingPort || 0}`);
|
firefoxArguments.push(`--remote-debugging-port=${debuggingPort || 0}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let userDataDir = null;
|
let userDataDir: string | undefined;
|
||||||
let isTempUserDataDir = true;
|
let isTempUserDataDir = true;
|
||||||
|
|
||||||
// Check for the profile argument, which will always be set even
|
// Check for the profile argument, which will always be set even
|
||||||
@ -342,7 +344,7 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
|
|
||||||
if (profileArgIndex !== -1) {
|
if (profileArgIndex !== -1) {
|
||||||
userDataDir = firefoxArguments[profileArgIndex + 1];
|
userDataDir = firefoxArguments[profileArgIndex + 1];
|
||||||
if (!fs.existsSync(userDataDir)) {
|
if (!userDataDir || !fs.existsSync(userDataDir)) {
|
||||||
throw new Error(`Firefox profile not found at '${userDataDir}'`);
|
throw new Error(`Firefox profile not found at '${userDataDir}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,16 +728,16 @@ function executablePathForChannel(channel: ChromeReleaseChannel): string {
|
|||||||
case 'win32':
|
case 'win32':
|
||||||
switch (channel) {
|
switch (channel) {
|
||||||
case 'chrome':
|
case 'chrome':
|
||||||
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`;
|
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe`;
|
||||||
break;
|
break;
|
||||||
case 'chrome-beta':
|
case 'chrome-beta':
|
||||||
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome Beta\\Application\\chrome.exe`;
|
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome Beta\\Application\\chrome.exe`;
|
||||||
break;
|
break;
|
||||||
case 'chrome-canary':
|
case 'chrome-canary':
|
||||||
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome SxS\\Application\\chrome.exe`;
|
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome SxS\\Application\\chrome.exe`;
|
||||||
break;
|
break;
|
||||||
case 'chrome-dev':
|
case 'chrome-dev':
|
||||||
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome Dev\\Application\\chrome.exe`;
|
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome Dev\\Application\\chrome.exe`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -802,9 +804,9 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): {
|
|||||||
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
||||||
if (!_isPuppeteerCore) {
|
if (!_isPuppeteerCore) {
|
||||||
const executablePath =
|
const executablePath =
|
||||||
process.env.PUPPETEER_EXECUTABLE_PATH ||
|
process.env['PUPPETEER_EXECUTABLE_PATH'] ||
|
||||||
process.env.npm_config_puppeteer_executable_path ||
|
process.env['npm_config_puppeteer_executable_path'] ||
|
||||||
process.env.npm_package_config_puppeteer_executable_path;
|
process.env['npm_package_config_puppeteer_executable_path'];
|
||||||
if (executablePath) {
|
if (executablePath) {
|
||||||
const missingText = !fs.existsSync(executablePath)
|
const missingText = !fs.existsSync(executablePath)
|
||||||
? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' +
|
? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' +
|
||||||
@ -822,9 +824,9 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): {
|
|||||||
return { executablePath: ubuntuChromiumPath, missingText: undefined };
|
return { executablePath: ubuntuChromiumPath, missingText: undefined };
|
||||||
}
|
}
|
||||||
downloadPath =
|
downloadPath =
|
||||||
process.env.PUPPETEER_DOWNLOAD_PATH ||
|
process.env['PUPPETEER_DOWNLOAD_PATH'] ||
|
||||||
process.env.npm_config_puppeteer_download_path ||
|
process.env['npm_config_puppeteer_download_path'] ||
|
||||||
process.env.npm_package_config_puppeteer_download_path;
|
process.env['npm_package_config_puppeteer_download_path'];
|
||||||
}
|
}
|
||||||
if (!_projectRoot) {
|
if (!_projectRoot) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -871,9 +873,9 @@ export default function Launcher(
|
|||||||
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
||||||
if (!product && !isPuppeteerCore)
|
if (!product && !isPuppeteerCore)
|
||||||
product =
|
product =
|
||||||
process.env.PUPPETEER_PRODUCT ||
|
process.env['PUPPETEER_PRODUCT'] ||
|
||||||
process.env.npm_config_puppeteer_product ||
|
process.env['npm_config_puppeteer_product'] ||
|
||||||
process.env.npm_package_config_puppeteer_product;
|
process.env['npm_package_config_puppeteer_product'];
|
||||||
switch (product) {
|
switch (product) {
|
||||||
case 'firefox':
|
case 'firefox':
|
||||||
return new FirefoxLauncher(
|
return new FirefoxLauncher(
|
||||||
|
@ -37,7 +37,7 @@ export class NodeWebSocketTransport implements ConnectionTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _ws: NodeWebSocket;
|
private _ws: NodeWebSocket;
|
||||||
onmessage?: (message: string) => void;
|
onmessage?: (message: NodeWebSocket.Data) => void;
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
|
|
||||||
constructor(ws: NodeWebSocket) {
|
constructor(ws: NodeWebSocket) {
|
||||||
@ -50,8 +50,6 @@ export class NodeWebSocketTransport implements ConnectionTransport {
|
|||||||
});
|
});
|
||||||
// Silently ignore all errors - we don't know what to do with them.
|
// Silently ignore all errors - we don't know what to do with them.
|
||||||
this._ws.addEventListener('error', () => {});
|
this._ws.addEventListener('error', () => {});
|
||||||
this.onmessage = null;
|
|
||||||
this.onclose = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message: string): void {
|
send(message: string): void {
|
||||||
|
@ -19,54 +19,63 @@ import {
|
|||||||
PuppeteerEventListener,
|
PuppeteerEventListener,
|
||||||
} from '../common/helper.js';
|
} from '../common/helper.js';
|
||||||
import { ConnectionTransport } from '../common/ConnectionTransport.js';
|
import { ConnectionTransport } from '../common/ConnectionTransport.js';
|
||||||
|
import { assert } from '../common/assert.js';
|
||||||
|
|
||||||
export class PipeTransport implements ConnectionTransport {
|
export class PipeTransport implements ConnectionTransport {
|
||||||
_pipeWrite: NodeJS.WritableStream;
|
_pipeWrite: NodeJS.WritableStream;
|
||||||
_pendingMessage: string;
|
|
||||||
_eventListeners: PuppeteerEventListener[];
|
_eventListeners: PuppeteerEventListener[];
|
||||||
|
|
||||||
|
_isClosed = false;
|
||||||
|
_pendingMessage = '';
|
||||||
|
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
onmessage?: () => void;
|
onmessage?: (value: string) => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
pipeWrite: NodeJS.WritableStream,
|
pipeWrite: NodeJS.WritableStream,
|
||||||
pipeRead: NodeJS.ReadableStream
|
pipeRead: NodeJS.ReadableStream
|
||||||
) {
|
) {
|
||||||
this._pipeWrite = pipeWrite;
|
this._pipeWrite = pipeWrite;
|
||||||
this._pendingMessage = '';
|
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(pipeRead, 'data', (buffer) =>
|
helper.addEventListener(pipeRead, 'data', (buffer) =>
|
||||||
this._dispatch(buffer)
|
this._dispatch(buffer)
|
||||||
),
|
),
|
||||||
helper.addEventListener(pipeRead, 'close', () => {
|
helper.addEventListener(pipeRead, 'close', () => {
|
||||||
if (this.onclose) this.onclose.call(null);
|
if (this.onclose) {
|
||||||
|
this.onclose.call(null);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
helper.addEventListener(pipeRead, 'error', debugError),
|
helper.addEventListener(pipeRead, 'error', debugError),
|
||||||
helper.addEventListener(pipeWrite, 'error', debugError),
|
helper.addEventListener(pipeWrite, 'error', debugError),
|
||||||
];
|
];
|
||||||
this.onmessage = null;
|
|
||||||
this.onclose = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message: string): void {
|
send(message: string): void {
|
||||||
|
assert(!this._isClosed, '`PipeTransport` is closed.');
|
||||||
|
|
||||||
this._pipeWrite.write(message);
|
this._pipeWrite.write(message);
|
||||||
this._pipeWrite.write('\0');
|
this._pipeWrite.write('\0');
|
||||||
}
|
}
|
||||||
|
|
||||||
_dispatch(buffer: Buffer): void {
|
_dispatch(buffer: Buffer): void {
|
||||||
|
assert(!this._isClosed, '`PipeTransport` is closed.');
|
||||||
|
|
||||||
let end = buffer.indexOf('\0');
|
let end = buffer.indexOf('\0');
|
||||||
if (end === -1) {
|
if (end === -1) {
|
||||||
this._pendingMessage += buffer.toString();
|
this._pendingMessage += buffer.toString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = this._pendingMessage + buffer.toString(undefined, 0, end);
|
const message = this._pendingMessage + buffer.toString(undefined, 0, end);
|
||||||
if (this.onmessage) this.onmessage.call(null, message);
|
if (this.onmessage) {
|
||||||
|
this.onmessage.call(null, message);
|
||||||
|
}
|
||||||
|
|
||||||
let start = end + 1;
|
let start = end + 1;
|
||||||
end = buffer.indexOf('\0', start);
|
end = buffer.indexOf('\0', start);
|
||||||
while (end !== -1) {
|
while (end !== -1) {
|
||||||
if (this.onmessage)
|
if (this.onmessage) {
|
||||||
this.onmessage.call(null, buffer.toString(undefined, start, end));
|
this.onmessage.call(null, buffer.toString(undefined, start, end));
|
||||||
|
}
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
end = buffer.indexOf('\0', start);
|
end = buffer.indexOf('\0', start);
|
||||||
}
|
}
|
||||||
@ -74,7 +83,7 @@ export class PipeTransport implements ConnectionTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this._pipeWrite = null;
|
this._isClosed = true;
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ export class PuppeteerNode extends Puppeteer {
|
|||||||
* @param options - Set of configurable options to set on the browser.
|
* @param options - Set of configurable options to set on the browser.
|
||||||
* @returns Promise which resolves to browser instance.
|
* @returns Promise which resolves to browser instance.
|
||||||
*/
|
*/
|
||||||
connect(options: ConnectOptions): Promise<Browser> {
|
override connect(options: ConnectOptions): Promise<Browser> {
|
||||||
if (options.product) this._productName = options.product;
|
if (options.product) this._productName = options.product;
|
||||||
return super.connect(options);
|
return super.connect(options);
|
||||||
}
|
}
|
||||||
|
@ -39,19 +39,19 @@ function getProduct(input: string): 'chrome' | 'firefox' {
|
|||||||
|
|
||||||
export async function downloadBrowser(): Promise<void> {
|
export async function downloadBrowser(): Promise<void> {
|
||||||
const downloadHost =
|
const downloadHost =
|
||||||
process.env.PUPPETEER_DOWNLOAD_HOST ||
|
process.env['PUPPETEER_DOWNLOAD_HOST'] ||
|
||||||
process.env.npm_config_puppeteer_download_host ||
|
process.env['npm_config_puppeteer_download_host'] ||
|
||||||
process.env.npm_package_config_puppeteer_download_host;
|
process.env['npm_package_config_puppeteer_download_host'];
|
||||||
const product = getProduct(
|
const product = getProduct(
|
||||||
process.env.PUPPETEER_PRODUCT ||
|
process.env['PUPPETEER_PRODUCT'] ||
|
||||||
process.env.npm_config_puppeteer_product ||
|
process.env['npm_config_puppeteer_product'] ||
|
||||||
process.env.npm_package_config_puppeteer_product ||
|
process.env['npm_package_config_puppeteer_product'] ||
|
||||||
'chrome'
|
'chrome'
|
||||||
);
|
);
|
||||||
const downloadPath =
|
const downloadPath =
|
||||||
process.env.PUPPETEER_DOWNLOAD_PATH ||
|
process.env['PUPPETEER_DOWNLOAD_PATH'] ||
|
||||||
process.env.npm_config_puppeteer_download_path ||
|
process.env['npm_config_puppeteer_download_path'] ||
|
||||||
process.env.npm_package_config_puppeteer_download_path;
|
process.env['npm_package_config_puppeteer_download_path'];
|
||||||
const browserFetcher = (puppeteer as PuppeteerNode).createBrowserFetcher({
|
const browserFetcher = (puppeteer as PuppeteerNode).createBrowserFetcher({
|
||||||
product,
|
product,
|
||||||
host: downloadHost,
|
host: downloadHost,
|
||||||
@ -63,8 +63,8 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
async function getRevision(): Promise<string> {
|
async function getRevision(): Promise<string> {
|
||||||
if (product === 'chrome') {
|
if (product === 'chrome') {
|
||||||
return (
|
return (
|
||||||
process.env.PUPPETEER_CHROMIUM_REVISION ||
|
process.env['PUPPETEER_CHROMIUM_REVISION'] ||
|
||||||
process.env.npm_config_puppeteer_chromium_revision ||
|
process.env['npm_config_puppeteer_chromium_revision'] ||
|
||||||
PUPPETEER_REVISIONS.chromium
|
PUPPETEER_REVISIONS.chromium
|
||||||
);
|
);
|
||||||
} else if (product === 'firefox') {
|
} else if (product === 'firefox') {
|
||||||
@ -92,14 +92,14 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
|
|
||||||
// Override current environment proxy settings with npm configuration, if any.
|
// Override current environment proxy settings with npm configuration, if any.
|
||||||
const NPM_HTTPS_PROXY =
|
const NPM_HTTPS_PROXY =
|
||||||
process.env.npm_config_https_proxy || process.env.npm_config_proxy;
|
process.env['npm_config_https_proxy'] || process.env['npm_config_proxy'];
|
||||||
const NPM_HTTP_PROXY =
|
const NPM_HTTP_PROXY =
|
||||||
process.env.npm_config_http_proxy || process.env.npm_config_proxy;
|
process.env['npm_config_http_proxy'] || process.env['npm_config_proxy'];
|
||||||
const NPM_NO_PROXY = process.env.npm_config_no_proxy;
|
const NPM_NO_PROXY = process.env['npm_config_no_proxy'];
|
||||||
|
|
||||||
if (NPM_HTTPS_PROXY) process.env.HTTPS_PROXY = NPM_HTTPS_PROXY;
|
if (NPM_HTTPS_PROXY) process.env['HTTPS_PROXY'] = NPM_HTTPS_PROXY;
|
||||||
if (NPM_HTTP_PROXY) process.env.HTTP_PROXY = NPM_HTTP_PROXY;
|
if (NPM_HTTP_PROXY) process.env['HTTP_PROXY'] = NPM_HTTP_PROXY;
|
||||||
if (NPM_NO_PROXY) process.env.NO_PROXY = NPM_NO_PROXY;
|
if (NPM_NO_PROXY) process.env['NO_PROXY'] = NPM_NO_PROXY;
|
||||||
|
|
||||||
function onSuccess(localRevisions: string[]): void {
|
function onSuccess(localRevisions: string[]): void {
|
||||||
logPolitely(
|
logPolitely(
|
||||||
@ -203,7 +203,7 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logPolitely(toBeLogged: unknown): void {
|
export function logPolitely(toBeLogged: unknown): void {
|
||||||
const logLevel = process.env.npm_config_loglevel || '';
|
const logLevel = process.env['npm_config_loglevel'] || '';
|
||||||
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
|
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -211,7 +211,7 @@ describe('Page.click', function () {
|
|||||||
.click('button.does-not-exist')
|
.click('button.does-not-exist')
|
||||||
.catch((error_) => (error = error_));
|
.catch((error_) => (error = error_));
|
||||||
expect(error.message).toBe(
|
expect(error.message).toBe(
|
||||||
'No node found for selector: button.does-not-exist'
|
'No element found for selector: button.does-not-exist'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// @see https://github.com/puppeteer/puppeteer/issues/161
|
// @see https://github.com/puppeteer/puppeteer/issues/161
|
||||||
|
@ -40,7 +40,7 @@ describe('Evaluation specs', function () {
|
|||||||
(bigint ? it : xit)('should transfer BigInt', async () => {
|
(bigint ? it : xit)('should transfer BigInt', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
|
|
||||||
const result = await page.evaluate((a: BigInt) => a, BigInt(42));
|
const result = await page.evaluate((a: bigint) => a, BigInt(42));
|
||||||
expect(result).toBe(BigInt(42));
|
expect(result).toBe(BigInt(42));
|
||||||
});
|
});
|
||||||
it('should transfer NaN', async () => {
|
it('should transfer NaN', async () => {
|
||||||
|
@ -60,30 +60,34 @@ export const getTestState = (): PuppeteerTestState =>
|
|||||||
state as PuppeteerTestState;
|
state as PuppeteerTestState;
|
||||||
|
|
||||||
const product =
|
const product =
|
||||||
process.env.PRODUCT || process.env.PUPPETEER_PRODUCT || 'Chromium';
|
process.env['PRODUCT'] || process.env['PUPPETEER_PRODUCT'] || 'Chromium';
|
||||||
|
|
||||||
const alternativeInstall = process.env.PUPPETEER_ALT_INSTALL || false;
|
const alternativeInstall = process.env['PUPPETEER_ALT_INSTALL'] || false;
|
||||||
|
|
||||||
const headless = (process.env.HEADLESS || 'true').trim().toLowerCase();
|
const headless = (process.env['HEADLESS'] || 'true').trim().toLowerCase();
|
||||||
const isHeadless = headless === 'true' || headless === 'chrome';
|
const isHeadless = headless === 'true' || headless === 'chrome';
|
||||||
const isFirefox = product === 'firefox';
|
const isFirefox = product === 'firefox';
|
||||||
const isChrome = product === 'Chromium';
|
const isChrome = product === 'Chromium';
|
||||||
|
|
||||||
let extraLaunchOptions = {};
|
let extraLaunchOptions = {};
|
||||||
try {
|
try {
|
||||||
extraLaunchOptions = JSON.parse(process.env.EXTRA_LAUNCH_OPTIONS || '{}');
|
extraLaunchOptions = JSON.parse(process.env['EXTRA_LAUNCH_OPTIONS'] || '{}');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(
|
if (error instanceof Error) {
|
||||||
`Error parsing EXTRA_LAUNCH_OPTIONS: ${error.message}. Skipping.`
|
console.warn(
|
||||||
);
|
`Error parsing EXTRA_LAUNCH_OPTIONS: ${error.message}. Skipping.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBrowserOptions = Object.assign(
|
const defaultBrowserOptions = Object.assign(
|
||||||
{
|
{
|
||||||
handleSIGINT: true,
|
handleSIGINT: true,
|
||||||
executablePath: process.env.BINARY,
|
executablePath: process.env['BINARY'],
|
||||||
headless: headless === 'chrome' ? ('chrome' as const) : isHeadless,
|
headless: headless === 'chrome' ? ('chrome' as const) : isHeadless,
|
||||||
dumpio: !!process.env.DUMPIO,
|
dumpio: !!process.env['DUMPIO'],
|
||||||
},
|
},
|
||||||
extraLaunchOptions
|
extraLaunchOptions
|
||||||
);
|
);
|
||||||
@ -178,7 +182,8 @@ export const itOnlyRegularInstall = (
|
|||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (alternativeInstall || process.env.BINARY) return xit(description, body);
|
if (alternativeInstall || process.env['BINARY'])
|
||||||
|
return xit(description, body);
|
||||||
else return it(description, body);
|
else return it(description, body);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -216,7 +221,7 @@ export const describeFailsFirefox = (
|
|||||||
export const describeChromeOnly = (
|
export const describeChromeOnly = (
|
||||||
description: string,
|
description: string,
|
||||||
body: (this: Mocha.Suite) => void
|
body: (this: Mocha.Suite) => void
|
||||||
): Mocha.Suite => {
|
): Mocha.Suite | void => {
|
||||||
if (isChrome) return describe(description, body);
|
if (isChrome) return describe(description, body);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -225,7 +230,7 @@ let coverageHooks = {
|
|||||||
afterAll: (): void => {},
|
afterAll: (): void => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.COVERAGE) {
|
if (process.env['COVERAGE']) {
|
||||||
coverageHooks = trackCoverage();
|
coverageHooks = trackCoverage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,21 +254,21 @@ export const setupTestBrowserHooks = (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await state.browser.close();
|
await state.browser!.close();
|
||||||
state.browser = null;
|
state.browser = undefined;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setupTestPageAndContextHooks = (): void => {
|
export const setupTestPageAndContextHooks = (): void => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
state.context = await state.browser.createIncognitoBrowserContext();
|
state.context = await state.browser!.createIncognitoBrowserContext();
|
||||||
state.page = await state.context.newPage();
|
state.page = await state.context.newPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await state.context.close();
|
await state.context!.close();
|
||||||
state.context = null;
|
state.context = undefined;
|
||||||
state.page = null;
|
state.page = undefined;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ describe('Target', function () {
|
|||||||
server.PREFIX + '/popup/popup.html'
|
server.PREFIX + '/popup/popup.html'
|
||||||
);
|
);
|
||||||
expect(createdTarget.opener()).toBe(page.target());
|
expect(createdTarget.opener()).toBe(page.target());
|
||||||
expect(page.target().opener()).toBe(null);
|
expect(page.target().opener()).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Browser.waitForTarget', () => {
|
describe('Browser.waitForTarget', () => {
|
||||||
|
@ -119,7 +119,7 @@ describeChromeOnly('Tracing', function () {
|
|||||||
expect(trace).toBeTruthy();
|
expect(trace).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null in case of Buffer error', async () => {
|
it('should return undefined in case of Buffer error', async () => {
|
||||||
const { server } = getTestState();
|
const { server } = getTestState();
|
||||||
|
|
||||||
await page.tracing.start({ screenshots: true });
|
await page.tracing.start({ screenshots: true });
|
||||||
@ -129,7 +129,7 @@ describeChromeOnly('Tracing', function () {
|
|||||||
throw 'error';
|
throw 'error';
|
||||||
};
|
};
|
||||||
const trace = await page.tracing.stop();
|
const trace = await page.tracing.stop();
|
||||||
expect(trace).toEqual(null);
|
expect(trace).toEqual(undefined);
|
||||||
Buffer.concat = oldBufferConcat;
|
Buffer.concat = oldBufferConcat;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "./tsconfig.test.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": true
|
"noEmit": true
|
||||||
},
|
}
|
||||||
"include": ["*.ts", "*.js"]
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "CommonJS"
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,29 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"target": "ES2019",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictPropertyInitialization": true,
|
||||||
|
"target": "ES2019",
|
||||||
|
"useUnknownInCatchVariables": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,13 @@ const fulfillSymbol = Symbol('fullfil callback');
|
|||||||
const rejectSymbol = Symbol('reject callback');
|
const rejectSymbol = Symbol('reject callback');
|
||||||
|
|
||||||
class TestServer {
|
class TestServer {
|
||||||
|
/** @type number */
|
||||||
PORT = undefined;
|
PORT = undefined;
|
||||||
|
/** @type string */
|
||||||
PREFIX = undefined;
|
PREFIX = undefined;
|
||||||
|
/** @type string */
|
||||||
CROSS_PROCESS_PREFIX = undefined;
|
CROSS_PROCESS_PREFIX = undefined;
|
||||||
|
/** @type string */
|
||||||
EMPTY_PAGE = undefined;
|
EMPTY_PAGE = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
2
vendor/tsconfig.cjs.json
vendored
2
vendor/tsconfig.cjs.json
vendored
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"mitt/dist"
|
"mitt/dist"
|
||||||
],
|
],
|
||||||
@ -7,6 +6,7 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"outDir": "../lib/cjs/vendor",
|
"outDir": "../lib/cjs/vendor",
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "node",
|
||||||
"strict": false
|
"strict": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
vendor/tsconfig.esm.json
vendored
2
vendor/tsconfig.esm.json
vendored
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"mitt/dist"
|
"mitt/dist"
|
||||||
],
|
],
|
||||||
@ -7,6 +6,7 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"outDir": "../lib/esm/vendor",
|
"outDir": "../lib/esm/vendor",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"strict": false
|
"strict": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user