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