mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: use curly
(#8519)
This commit is contained in:
parent
0678343b53
commit
e6442dd767
@ -21,6 +21,8 @@ module.exports = {
|
|||||||
extends: ['plugin:prettier/recommended'],
|
extends: ['plugin:prettier/recommended'],
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
|
// Brackets keep code readable.
|
||||||
|
curly: [2, 'all'],
|
||||||
// Error if files are not formatted with Prettier correctly.
|
// Error if files are not formatted with Prettier correctly.
|
||||||
'prettier/prettier': 2,
|
'prettier/prettier': 2,
|
||||||
// syntax preferences
|
// syntax preferences
|
||||||
@ -130,6 +132,8 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
plugins: ['eslint-plugin-tsdoc'],
|
plugins: ['eslint-plugin-tsdoc'],
|
||||||
rules: {
|
rules: {
|
||||||
|
// Brackets keep code readable.
|
||||||
|
curly: [2, 'all'],
|
||||||
// Error if comments do not adhere to `tsdoc`.
|
// Error if comments do not adhere to `tsdoc`.
|
||||||
'tsdoc/syntax': 2,
|
'tsdoc/syntax': 2,
|
||||||
'no-unused-vars': 0,
|
'no-unused-vars': 0,
|
||||||
|
@ -23,8 +23,11 @@ const puppeteer = require('puppeteer');
|
|||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.resourceType() === 'image') request.abort();
|
if (request.resourceType() === 'image') {
|
||||||
else request.continue();
|
request.abort();
|
||||||
|
} else {
|
||||||
|
request.continue();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await page.goto('https://news.google.com/news/');
|
await page.goto('https://news.google.com/news/');
|
||||||
await page.screenshot({ path: 'news.png', fullPage: true });
|
await page.screenshot({ path: 'news.png', fullPage: true });
|
||||||
|
@ -196,13 +196,19 @@ export class Accessibility {
|
|||||||
needle = defaultRoot.find(
|
needle = defaultRoot.find(
|
||||||
(node) => node.payload.backendDOMNodeId === backendNodeId
|
(node) => node.payload.backendDOMNodeId === backendNodeId
|
||||||
);
|
);
|
||||||
if (!needle) return null;
|
if (!needle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!interestingOnly) {
|
||||||
|
return this.serializeTree(needle)[0] ?? null;
|
||||||
}
|
}
|
||||||
if (!interestingOnly) return this.serializeTree(needle)[0] ?? null;
|
|
||||||
|
|
||||||
const interestingNodes = new Set<AXNode>();
|
const interestingNodes = new Set<AXNode>();
|
||||||
this.collectInterestingNodes(interestingNodes, defaultRoot, false);
|
this.collectInterestingNodes(interestingNodes, defaultRoot, false);
|
||||||
if (!interestingNodes.has(needle)) return null;
|
if (!interestingNodes.has(needle)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this.serializeTree(needle, interestingNodes)[0] ?? null;
|
return this.serializeTree(needle, interestingNodes)[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,13 +217,18 @@ export class Accessibility {
|
|||||||
interestingNodes?: Set<AXNode>
|
interestingNodes?: Set<AXNode>
|
||||||
): SerializedAXNode[] {
|
): SerializedAXNode[] {
|
||||||
const children: SerializedAXNode[] = [];
|
const children: SerializedAXNode[] = [];
|
||||||
for (const child of node.children)
|
for (const child of node.children) {
|
||||||
children.push(...this.serializeTree(child, interestingNodes));
|
children.push(...this.serializeTree(child, interestingNodes));
|
||||||
|
}
|
||||||
|
|
||||||
if (interestingNodes && !interestingNodes.has(node)) return children;
|
if (interestingNodes && !interestingNodes.has(node)) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
const serializedNode = node.serialize();
|
const serializedNode = node.serialize();
|
||||||
if (children.length) serializedNode.children = children;
|
if (children.length) {
|
||||||
|
serializedNode.children = children;
|
||||||
|
}
|
||||||
return [serializedNode];
|
return [serializedNode];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,11 +237,16 @@ export class Accessibility {
|
|||||||
node: AXNode,
|
node: AXNode,
|
||||||
insideControl: boolean
|
insideControl: boolean
|
||||||
): void {
|
): void {
|
||||||
if (node.isInteresting(insideControl)) collection.add(node);
|
if (node.isInteresting(insideControl)) {
|
||||||
if (node.isLeafNode()) return;
|
collection.add(node);
|
||||||
|
}
|
||||||
|
if (node.isLeafNode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
insideControl = insideControl || node.isControl();
|
insideControl = insideControl || node.isControl();
|
||||||
for (const child of node.children)
|
for (const child of node.children) {
|
||||||
this.collectInterestingNodes(collection, child, insideControl);
|
this.collectInterestingNodes(collection, child, insideControl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,14 +274,22 @@ class AXNode {
|
|||||||
this.#richlyEditable = property.value.value === 'richtext';
|
this.#richlyEditable = property.value.value === 'richtext';
|
||||||
this.#editable = true;
|
this.#editable = true;
|
||||||
}
|
}
|
||||||
if (property.name === 'focusable') this.#focusable = property.value.value;
|
if (property.name === 'focusable') {
|
||||||
if (property.name === 'hidden') this.#hidden = property.value.value;
|
this.#focusable = property.value.value;
|
||||||
|
}
|
||||||
|
if (property.name === 'hidden') {
|
||||||
|
this.#hidden = property.value.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#isPlainTextField(): boolean {
|
#isPlainTextField(): boolean {
|
||||||
if (this.#richlyEditable) return false;
|
if (this.#richlyEditable) {
|
||||||
if (this.#editable) return true;
|
return false;
|
||||||
|
}
|
||||||
|
if (this.#editable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return this.#role === 'textbox' || this.#role === 'searchbox';
|
return this.#role === 'textbox' || this.#role === 'searchbox';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,22 +312,30 @@ class AXNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public find(predicate: (x: AXNode) => boolean): AXNode | null {
|
public find(predicate: (x: AXNode) => boolean): AXNode | null {
|
||||||
if (predicate(this)) return this;
|
if (predicate(this)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
for (const child of this.children) {
|
for (const child of this.children) {
|
||||||
const result = child.find(predicate);
|
const result = child.find(predicate);
|
||||||
if (result) return result;
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isLeafNode(): boolean {
|
public isLeafNode(): boolean {
|
||||||
if (!this.children.length) return true;
|
if (!this.children.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// These types of objects may have children that we use as internal
|
// These types of objects may have children that we use as internal
|
||||||
// implementation details, but we want to expose them as leaves to platform
|
// implementation details, but we want to expose them as leaves to platform
|
||||||
// accessibility APIs because screen readers might be confused if they find
|
// accessibility APIs because screen readers might be confused if they find
|
||||||
// any children.
|
// any children.
|
||||||
if (this.#isPlainTextField() || this.#isTextOnlyObject()) return true;
|
if (this.#isPlainTextField() || this.#isTextOnlyObject()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Roles whose children are only presentational according to the ARIA and
|
// Roles whose children are only presentational according to the ARIA and
|
||||||
// HTML5 Specs should be hidden from screen readers.
|
// HTML5 Specs should be hidden from screen readers.
|
||||||
@ -324,9 +356,15 @@ class AXNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Here and below: Android heuristics
|
// Here and below: Android heuristics
|
||||||
if (this.#hasFocusableChild()) return false;
|
if (this.#hasFocusableChild()) {
|
||||||
if (this.#focusable && this.#name) return true;
|
return false;
|
||||||
if (this.#role === 'heading' && this.#name) return true;
|
}
|
||||||
|
if (this.#focusable && this.#name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.#role === 'heading' && this.#name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,27 +399,41 @@ class AXNode {
|
|||||||
|
|
||||||
public isInteresting(insideControl: boolean): boolean {
|
public isInteresting(insideControl: boolean): boolean {
|
||||||
const role = this.#role;
|
const role = this.#role;
|
||||||
if (role === 'Ignored' || this.#hidden || this.#ignored) return false;
|
if (role === 'Ignored' || this.#hidden || this.#ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.#focusable || this.#richlyEditable) return true;
|
if (this.#focusable || this.#richlyEditable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// If it's not focusable but has a control role, then it's interesting.
|
// If it's not focusable but has a control role, then it's interesting.
|
||||||
if (this.isControl()) return true;
|
if (this.isControl()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// A non focusable child of a control is not interesting
|
// A non focusable child of a control is not interesting
|
||||||
if (insideControl) return false;
|
if (insideControl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.isLeafNode() && !!this.#name;
|
return this.isLeafNode() && !!this.#name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public serialize(): SerializedAXNode {
|
public serialize(): SerializedAXNode {
|
||||||
const properties = new Map<string, number | string | boolean>();
|
const properties = new Map<string, number | string | boolean>();
|
||||||
for (const property of this.payload.properties || [])
|
for (const property of this.payload.properties || []) {
|
||||||
properties.set(property.name.toLowerCase(), property.value.value);
|
properties.set(property.name.toLowerCase(), property.value.value);
|
||||||
if (this.payload.name) properties.set('name', this.payload.name.value);
|
}
|
||||||
if (this.payload.value) properties.set('value', this.payload.value.value);
|
if (this.payload.name) {
|
||||||
if (this.payload.description)
|
properties.set('name', this.payload.name.value);
|
||||||
|
}
|
||||||
|
if (this.payload.value) {
|
||||||
|
properties.set('value', this.payload.value.value);
|
||||||
|
}
|
||||||
|
if (this.payload.description) {
|
||||||
properties.set('description', this.payload.description.value);
|
properties.set('description', this.payload.description.value);
|
||||||
|
}
|
||||||
|
|
||||||
const node: SerializedAXNode = {
|
const node: SerializedAXNode = {
|
||||||
role: this.#role,
|
role: this.#role,
|
||||||
@ -407,7 +459,9 @@ class AXNode {
|
|||||||
properties.get(key) as string;
|
properties.get(key) as string;
|
||||||
|
|
||||||
for (const userStringProperty of userStringProperties) {
|
for (const userStringProperty of userStringProperties) {
|
||||||
if (!properties.has(userStringProperty)) continue;
|
if (!properties.has(userStringProperty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
node[userStringProperty] = getUserStringPropertyValue(userStringProperty);
|
node[userStringProperty] = getUserStringPropertyValue(userStringProperty);
|
||||||
}
|
}
|
||||||
@ -440,17 +494,22 @@ class AXNode {
|
|||||||
// RootWebArea's treat focus differently than other nodes. They report whether
|
// RootWebArea's treat focus differently than other nodes. They report whether
|
||||||
// their frame has focus, not whether focus is specifically on the root
|
// their frame has focus, not whether focus is specifically on the root
|
||||||
// node.
|
// node.
|
||||||
if (booleanProperty === 'focused' && this.#role === 'RootWebArea')
|
if (booleanProperty === 'focused' && this.#role === 'RootWebArea') {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
const value = getBooleanPropertyValue(booleanProperty);
|
const value = getBooleanPropertyValue(booleanProperty);
|
||||||
if (!value) continue;
|
if (!value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
node[booleanProperty] = getBooleanPropertyValue(booleanProperty);
|
node[booleanProperty] = getBooleanPropertyValue(booleanProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
type TristateProperty = 'checked' | 'pressed';
|
type TristateProperty = 'checked' | 'pressed';
|
||||||
const tristateProperties: TristateProperty[] = ['checked', 'pressed'];
|
const tristateProperties: TristateProperty[] = ['checked', 'pressed'];
|
||||||
for (const tristateProperty of tristateProperties) {
|
for (const tristateProperty of tristateProperties) {
|
||||||
if (!properties.has(tristateProperty)) continue;
|
if (!properties.has(tristateProperty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const value = properties.get(tristateProperty);
|
const value = properties.get(tristateProperty);
|
||||||
node[tristateProperty] =
|
node[tristateProperty] =
|
||||||
value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
|
value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
|
||||||
@ -465,7 +524,9 @@ class AXNode {
|
|||||||
const getNumericalPropertyValue = (key: NumbericalProperty): number =>
|
const getNumericalPropertyValue = (key: NumbericalProperty): number =>
|
||||||
properties.get(key) as number;
|
properties.get(key) as number;
|
||||||
for (const numericalProperty of numericalProperties) {
|
for (const numericalProperty of numericalProperties) {
|
||||||
if (!properties.has(numericalProperty)) continue;
|
if (!properties.has(numericalProperty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
node[numericalProperty] = getNumericalPropertyValue(numericalProperty);
|
node[numericalProperty] = getNumericalPropertyValue(numericalProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +545,9 @@ class AXNode {
|
|||||||
properties.get(key) as string;
|
properties.get(key) as string;
|
||||||
for (const tokenProperty of tokenProperties) {
|
for (const tokenProperty of tokenProperties) {
|
||||||
const value = getTokenPropertyValue(tokenProperty);
|
const value = getTokenPropertyValue(tokenProperty);
|
||||||
if (!value || value === 'false') continue;
|
if (!value || value === 'false') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
node[tokenProperty] = getTokenPropertyValue(tokenProperty);
|
node[tokenProperty] = getTokenPropertyValue(tokenProperty);
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
@ -492,11 +555,13 @@ class AXNode {
|
|||||||
|
|
||||||
public static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode {
|
public static createTree(payloads: Protocol.Accessibility.AXNode[]): AXNode {
|
||||||
const nodeById = new Map<string, AXNode>();
|
const nodeById = new Map<string, AXNode>();
|
||||||
for (const payload of payloads)
|
for (const payload of payloads) {
|
||||||
nodeById.set(payload.nodeId, new AXNode(payload));
|
nodeById.set(payload.nodeId, new AXNode(payload));
|
||||||
|
}
|
||||||
for (const node of nodeById.values()) {
|
for (const node of nodeById.values()) {
|
||||||
for (const childId of node.payload.childIds || [])
|
for (const childId of node.payload.childIds || []) {
|
||||||
node.children.push(nodeById.get(childId)!);
|
node.children.push(nodeById.get(childId)!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nodeById.values().next().value;
|
return nodeById.values().next().value;
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,9 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (defaultName && !queryOptions.name)
|
if (defaultName && !queryOptions.name) {
|
||||||
queryOptions.name = normalizeValue(defaultName);
|
queryOptions.name = normalizeValue(defaultName);
|
||||||
|
}
|
||||||
return queryOptions;
|
return queryOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,11 +285,12 @@ export class Browser extends EventEmitter {
|
|||||||
|
|
||||||
this.#defaultContext = new BrowserContext(this.#connection, this);
|
this.#defaultContext = new BrowserContext(this.#connection, this);
|
||||||
this.#contexts = new Map();
|
this.#contexts = new Map();
|
||||||
for (const contextId of contextIds)
|
for (const contextId of contextIds) {
|
||||||
this.#contexts.set(
|
this.#contexts.set(
|
||||||
contextId,
|
contextId,
|
||||||
new BrowserContext(this.#connection, this, contextId)
|
new BrowserContext(this.#connection, this, contextId)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.#targets = new Map();
|
this.#targets = new Map();
|
||||||
this.#connection.on(ConnectionEmittedEvents.Disconnected, () =>
|
this.#connection.on(ConnectionEmittedEvents.Disconnected, () =>
|
||||||
@ -441,7 +442,9 @@ export class Browser extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #targetDestroyed(event: { targetId: string }): Promise<void> {
|
async #targetDestroyed(event: { targetId: string }): Promise<void> {
|
||||||
if (this.#ignoredTargets.has(event.targetId)) return;
|
if (this.#ignoredTargets.has(event.targetId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const target = this.#targets.get(event.targetId);
|
const target = this.#targets.get(event.targetId);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -460,7 +463,9 @@ export class Browser extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#targetInfoChanged(event: Protocol.Target.TargetInfoChangedEvent): void {
|
#targetInfoChanged(event: Protocol.Target.TargetInfoChangedEvent): void {
|
||||||
if (this.#ignoredTargets.has(event.targetInfo.targetId)) return;
|
if (this.#ignoredTargets.has(event.targetInfo.targetId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const target = this.#targets.get(event.targetInfo.targetId);
|
const target = this.#targets.get(event.targetInfo.targetId);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -580,7 +585,9 @@ export class Browser extends EventEmitter {
|
|||||||
this.on(BrowserEmittedEvents.TargetCreated, check);
|
this.on(BrowserEmittedEvents.TargetCreated, check);
|
||||||
this.on(BrowserEmittedEvents.TargetChanged, check);
|
this.on(BrowserEmittedEvents.TargetChanged, check);
|
||||||
try {
|
try {
|
||||||
if (!timeout) return await targetPromise;
|
if (!timeout) {
|
||||||
|
return await targetPromise;
|
||||||
|
}
|
||||||
this.targets().forEach(check);
|
this.targets().forEach(check);
|
||||||
return await waitWithTimeout(targetPromise, 'target', timeout);
|
return await waitWithTimeout(targetPromise, 'target', timeout);
|
||||||
} finally {
|
} finally {
|
||||||
@ -827,8 +834,9 @@ export class BrowserContext extends EventEmitter {
|
|||||||
const protocolPermissions = permissions.map((permission) => {
|
const protocolPermissions = permissions.map((permission) => {
|
||||||
const protocolPermission =
|
const protocolPermission =
|
||||||
WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission);
|
WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission);
|
||||||
if (!protocolPermission)
|
if (!protocolPermission) {
|
||||||
throw new Error('Unknown permission: ' + permission);
|
throw new Error('Unknown permission: ' + permission);
|
||||||
|
}
|
||||||
return protocolPermission;
|
return protocolPermission;
|
||||||
});
|
});
|
||||||
await this.#connection.send('Browser.grantPermissions', {
|
await this.#connection.send('Browser.grantPermissions', {
|
||||||
|
@ -34,10 +34,14 @@ export class BrowserWebSocketTransport implements ConnectionTransport {
|
|||||||
constructor(ws: WebSocket) {
|
constructor(ws: WebSocket) {
|
||||||
this.#ws = ws;
|
this.#ws = ws;
|
||||||
this.#ws.addEventListener('message', (event) => {
|
this.#ws.addEventListener('message', (event) => {
|
||||||
if (this.onmessage) this.onmessage.call(null, event.data);
|
if (this.onmessage) {
|
||||||
|
this.onmessage.call(null, event.data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.#ws.addEventListener('close', () => {
|
this.#ws.addEventListener('close', () => {
|
||||||
if (this.onclose) this.onclose.call(null);
|
if (this.onclose) {
|
||||||
|
this.onclose.call(null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Silently ignore all errors - we don't know what to do with them.
|
// Silently ignore all errors - we don't know what to do with them.
|
||||||
this.#ws.addEventListener('error', () => {});
|
this.#ws.addEventListener('error', () => {});
|
||||||
|
@ -129,7 +129,9 @@ export class Connection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #onMessage(message: string): Promise<void> {
|
async #onMessage(message: string): Promise<void> {
|
||||||
if (this.#delay) await new Promise((f) => setTimeout(f, this.#delay));
|
if (this.#delay) {
|
||||||
|
await new Promise((f) => setTimeout(f, this.#delay));
|
||||||
|
}
|
||||||
debugProtocolReceive(message);
|
debugProtocolReceive(message);
|
||||||
const object = JSON.parse(message);
|
const object = JSON.parse(message);
|
||||||
if (object.method === 'Target.attachedToTarget') {
|
if (object.method === 'Target.attachedToTarget') {
|
||||||
@ -159,17 +161,21 @@ export class Connection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
if (object.sessionId) {
|
if (object.sessionId) {
|
||||||
const session = this.#sessions.get(object.sessionId);
|
const session = this.#sessions.get(object.sessionId);
|
||||||
if (session) session._onMessage(object);
|
if (session) {
|
||||||
|
session._onMessage(object);
|
||||||
|
}
|
||||||
} else if (object.id) {
|
} else if (object.id) {
|
||||||
const callback = this.#callbacks.get(object.id);
|
const callback = this.#callbacks.get(object.id);
|
||||||
// Callbacks could be all rejected if someone has called `.dispose()`.
|
// Callbacks could be all rejected if someone has called `.dispose()`.
|
||||||
if (callback) {
|
if (callback) {
|
||||||
this.#callbacks.delete(object.id);
|
this.#callbacks.delete(object.id);
|
||||||
if (object.error)
|
if (object.error) {
|
||||||
callback.reject(
|
callback.reject(
|
||||||
createProtocolError(callback.error, callback.method, object)
|
createProtocolError(callback.error, callback.method, object)
|
||||||
);
|
);
|
||||||
else callback.resolve(object.result);
|
} else {
|
||||||
|
callback.resolve(object.result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.emit(object.method, object.params);
|
this.emit(object.method, object.params);
|
||||||
@ -177,19 +183,24 @@ export class Connection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onClose(): void {
|
#onClose(): void {
|
||||||
if (this.#closed) return;
|
if (this.#closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#closed = true;
|
this.#closed = true;
|
||||||
this.#transport.onmessage = undefined;
|
this.#transport.onmessage = undefined;
|
||||||
this.#transport.onclose = undefined;
|
this.#transport.onclose = undefined;
|
||||||
for (const callback of this.#callbacks.values())
|
for (const callback of this.#callbacks.values()) {
|
||||||
callback.reject(
|
callback.reject(
|
||||||
rewriteError(
|
rewriteError(
|
||||||
callback.error,
|
callback.error,
|
||||||
`Protocol error (${callback.method}): Target closed.`
|
`Protocol error (${callback.method}): Target closed.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
this.#callbacks.clear();
|
this.#callbacks.clear();
|
||||||
for (const session of this.#sessions.values()) session._onClosed();
|
for (const session of this.#sessions.values()) {
|
||||||
|
session._onClosed();
|
||||||
|
}
|
||||||
this.#sessions.clear();
|
this.#sessions.clear();
|
||||||
this.emit(ConnectionEmittedEvents.Disconnected);
|
this.emit(ConnectionEmittedEvents.Disconnected);
|
||||||
}
|
}
|
||||||
@ -287,7 +298,7 @@ export class CDPSession extends EventEmitter {
|
|||||||
method: T,
|
method: T,
|
||||||
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
|
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
|
||||||
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
||||||
if (!this.#connection)
|
if (!this.#connection) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(
|
new Error(
|
||||||
`Protocol error (${method}): Session closed. Most likely the ${
|
`Protocol error (${method}): Session closed. Most likely the ${
|
||||||
@ -295,6 +306,7 @@ export class CDPSession extends EventEmitter {
|
|||||||
} has been closed.`
|
} has been closed.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// See the comment in Connection#send explaining why we do this.
|
// See the comment in Connection#send explaining why we do this.
|
||||||
const params = paramArgs.length ? paramArgs[0] : undefined;
|
const params = paramArgs.length ? paramArgs[0] : undefined;
|
||||||
@ -322,11 +334,13 @@ export class CDPSession extends EventEmitter {
|
|||||||
const callback = object.id ? this.#callbacks.get(object.id) : undefined;
|
const callback = object.id ? this.#callbacks.get(object.id) : undefined;
|
||||||
if (object.id && callback) {
|
if (object.id && callback) {
|
||||||
this.#callbacks.delete(object.id);
|
this.#callbacks.delete(object.id);
|
||||||
if (object.error)
|
if (object.error) {
|
||||||
callback.reject(
|
callback.reject(
|
||||||
createProtocolError(callback.error, callback.method, object)
|
createProtocolError(callback.error, callback.method, object)
|
||||||
);
|
);
|
||||||
else callback.resolve(object.result);
|
} else {
|
||||||
|
callback.resolve(object.result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assert(!object.id);
|
assert(!object.id);
|
||||||
this.emit(object.method, object.params);
|
this.emit(object.method, object.params);
|
||||||
@ -338,12 +352,13 @@ export class CDPSession extends EventEmitter {
|
|||||||
* won't emit any events and can't be used to send messages.
|
* won't emit any events and can't be used to send messages.
|
||||||
*/
|
*/
|
||||||
async detach(): Promise<void> {
|
async detach(): Promise<void> {
|
||||||
if (!this.#connection)
|
if (!this.#connection) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Session already detached. Most likely the ${
|
`Session already detached. Most likely the ${
|
||||||
this.#targetType
|
this.#targetType
|
||||||
} has been closed.`
|
} has been closed.`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await this.#connection.send('Target.detachFromTarget', {
|
await this.#connection.send('Target.detachFromTarget', {
|
||||||
sessionId: this.#sessionId,
|
sessionId: this.#sessionId,
|
||||||
});
|
});
|
||||||
@ -353,13 +368,14 @@ export class CDPSession extends EventEmitter {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_onClosed(): void {
|
_onClosed(): void {
|
||||||
for (const callback of this.#callbacks.values())
|
for (const callback of this.#callbacks.values()) {
|
||||||
callback.reject(
|
callback.reject(
|
||||||
rewriteError(
|
rewriteError(
|
||||||
callback.error,
|
callback.error,
|
||||||
`Protocol error (${callback.method}): Target closed.`
|
`Protocol error (${callback.method}): Target closed.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
this.#callbacks.clear();
|
this.#callbacks.clear();
|
||||||
this.#connection = undefined;
|
this.#connection = undefined;
|
||||||
this.emit(CDPSessionEmittedEvents.Disconnected);
|
this.emit(CDPSessionEmittedEvents.Disconnected);
|
||||||
@ -379,7 +395,9 @@ function createProtocolError(
|
|||||||
object: { error: { message: string; data: any; code: number } }
|
object: { error: { message: string; data: any; code: number } }
|
||||||
): Error {
|
): Error {
|
||||||
let message = `Protocol error (${method}): ${object.error.message}`;
|
let message = `Protocol error (${method}): ${object.error.message}`;
|
||||||
if ('data' in object.error) message += ` ${object.error.data}`;
|
if ('data' in object.error) {
|
||||||
|
message += ` ${object.error.data}`;
|
||||||
|
}
|
||||||
return rewriteError(error, message, object.error.message);
|
return rewriteError(error, message, object.error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +244,9 @@ export class JSCoverage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onExecutionContextsCleared(): void {
|
#onExecutionContextsCleared(): void {
|
||||||
if (!this.#resetOnNavigation) return;
|
if (!this.#resetOnNavigation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#scriptURLs.clear();
|
this.#scriptURLs.clear();
|
||||||
this.#scriptSources.clear();
|
this.#scriptSources.clear();
|
||||||
}
|
}
|
||||||
@ -253,9 +255,13 @@ export class JSCoverage {
|
|||||||
event: Protocol.Debugger.ScriptParsedEvent
|
event: Protocol.Debugger.ScriptParsedEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Ignore puppeteer-injected scripts
|
// Ignore puppeteer-injected scripts
|
||||||
if (event.url === EVALUATION_SCRIPT_URL) return;
|
if (event.url === EVALUATION_SCRIPT_URL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
|
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
|
||||||
if (!event.url && !this.#reportAnonymousScripts) return;
|
if (!event.url && !this.#reportAnonymousScripts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await this.#client.send('Debugger.getScriptSource', {
|
const response = await this.#client.send('Debugger.getScriptSource', {
|
||||||
scriptId: event.scriptId,
|
scriptId: event.scriptId,
|
||||||
@ -286,12 +292,17 @@ export class JSCoverage {
|
|||||||
|
|
||||||
for (const entry of profileResponse.result) {
|
for (const entry of profileResponse.result) {
|
||||||
let url = this.#scriptURLs.get(entry.scriptId);
|
let url = this.#scriptURLs.get(entry.scriptId);
|
||||||
if (!url && this.#reportAnonymousScripts)
|
if (!url && this.#reportAnonymousScripts) {
|
||||||
url = 'debugger://VM' + entry.scriptId;
|
url = 'debugger://VM' + entry.scriptId;
|
||||||
|
}
|
||||||
const text = this.#scriptSources.get(entry.scriptId);
|
const text = this.#scriptSources.get(entry.scriptId);
|
||||||
if (text === undefined || url === undefined) continue;
|
if (text === undefined || url === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const flattenRanges = [];
|
const flattenRanges = [];
|
||||||
for (const func of entry.functions) flattenRanges.push(...func.ranges);
|
for (const func of entry.functions) {
|
||||||
|
flattenRanges.push(...func.ranges);
|
||||||
|
}
|
||||||
const ranges = convertToDisjointRanges(flattenRanges);
|
const ranges = convertToDisjointRanges(flattenRanges);
|
||||||
if (!this.#includeRawScriptCoverage) {
|
if (!this.#includeRawScriptCoverage) {
|
||||||
coverage.push({ url, ranges, text });
|
coverage.push({ url, ranges, text });
|
||||||
@ -345,7 +356,9 @@ export class CSSCoverage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onExecutionContextsCleared(): void {
|
#onExecutionContextsCleared(): void {
|
||||||
if (!this.#resetOnNavigation) return;
|
if (!this.#resetOnNavigation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#stylesheetURLs.clear();
|
this.#stylesheetURLs.clear();
|
||||||
this.#stylesheetSources.clear();
|
this.#stylesheetSources.clear();
|
||||||
}
|
}
|
||||||
@ -353,7 +366,9 @@ export class CSSCoverage {
|
|||||||
async #onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise<void> {
|
async #onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise<void> {
|
||||||
const header = event.header;
|
const header = event.header;
|
||||||
// Ignore anonymous scripts
|
// Ignore anonymous scripts
|
||||||
if (!header.sourceURL) return;
|
if (!header.sourceURL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await this.#client.send('CSS.getStyleSheetText', {
|
const response = await this.#client.send('CSS.getStyleSheetText', {
|
||||||
styleSheetId: header.styleSheetId,
|
styleSheetId: header.styleSheetId,
|
||||||
@ -420,13 +435,19 @@ function convertToDisjointRanges(
|
|||||||
// Sort points to form a valid parenthesis sequence.
|
// Sort points to form a valid parenthesis sequence.
|
||||||
points.sort((a, b) => {
|
points.sort((a, b) => {
|
||||||
// Sort with increasing offsets.
|
// Sort with increasing offsets.
|
||||||
if (a.offset !== b.offset) return a.offset - b.offset;
|
if (a.offset !== b.offset) {
|
||||||
|
return a.offset - b.offset;
|
||||||
|
}
|
||||||
// All "end" points should go before "start" points.
|
// All "end" points should go before "start" points.
|
||||||
if (a.type !== b.type) return b.type - a.type;
|
if (a.type !== b.type) {
|
||||||
|
return b.type - a.type;
|
||||||
|
}
|
||||||
const aLength = a.range.endOffset - a.range.startOffset;
|
const aLength = a.range.endOffset - a.range.startOffset;
|
||||||
const bLength = b.range.endOffset - b.range.startOffset;
|
const bLength = b.range.endOffset - b.range.startOffset;
|
||||||
// For two "start" points, the one with longer range goes first.
|
// For two "start" points, the one with longer range goes first.
|
||||||
if (a.type === 0) return bLength - aLength;
|
if (a.type === 0) {
|
||||||
|
return bLength - aLength;
|
||||||
|
}
|
||||||
// For two "end" points, the one with shorter range goes first.
|
// For two "end" points, the one with shorter range goes first.
|
||||||
return aLength - bLength;
|
return aLength - bLength;
|
||||||
});
|
});
|
||||||
@ -445,13 +466,18 @@ function convertToDisjointRanges(
|
|||||||
hitCountStack[hitCountStack.length - 1]! > 0
|
hitCountStack[hitCountStack.length - 1]! > 0
|
||||||
) {
|
) {
|
||||||
const lastResult = results[results.length - 1];
|
const lastResult = results[results.length - 1];
|
||||||
if (lastResult && lastResult.end === lastOffset)
|
if (lastResult && lastResult.end === lastOffset) {
|
||||||
lastResult.end = point.offset;
|
lastResult.end = point.offset;
|
||||||
else results.push({ start: lastOffset, end: point.offset });
|
} else {
|
||||||
|
results.push({ start: lastOffset, end: point.offset });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lastOffset = point.offset;
|
lastOffset = point.offset;
|
||||||
if (point.type === 0) hitCountStack.push(point.range.count);
|
if (point.type === 0) {
|
||||||
else hitCountStack.pop();
|
hitCountStack.push(point.range.count);
|
||||||
|
} else {
|
||||||
|
hitCountStack.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Filter out empty ranges.
|
// Filter out empty ranges.
|
||||||
return results.filter((range) => range.end - range.start > 1);
|
return results.filter((range) => range.end - range.start > 1);
|
||||||
|
@ -143,7 +143,9 @@ export class DOMWorld {
|
|||||||
this.#ctxBindings.clear();
|
this.#ctxBindings.clear();
|
||||||
this.#contextResolveCallback?.call(null, context);
|
this.#contextResolveCallback?.call(null, context);
|
||||||
this.#contextResolveCallback = null;
|
this.#contextResolveCallback = null;
|
||||||
for (const waitTask of this._waitTasks) waitTask.rerun();
|
for (const waitTask of this._waitTasks) {
|
||||||
|
waitTask.rerun();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.#documentPromise = null;
|
this.#documentPromise = null;
|
||||||
this.#contextPromise = new Promise((fulfill) => {
|
this.#contextPromise = new Promise((fulfill) => {
|
||||||
@ -165,19 +167,22 @@ export class DOMWorld {
|
|||||||
_detach(): void {
|
_detach(): void {
|
||||||
this.#detached = true;
|
this.#detached = true;
|
||||||
this.#client.off('Runtime.bindingCalled', this.#onBindingCalled);
|
this.#client.off('Runtime.bindingCalled', this.#onBindingCalled);
|
||||||
for (const waitTask of this._waitTasks)
|
for (const waitTask of this._waitTasks) {
|
||||||
waitTask.terminate(
|
waitTask.terminate(
|
||||||
new Error('waitForFunction failed: frame got detached.')
|
new Error('waitForFunction failed: frame got detached.')
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executionContext(): Promise<ExecutionContext> {
|
executionContext(): Promise<ExecutionContext> {
|
||||||
if (this.#detached)
|
if (this.#detached) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
|
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
|
||||||
);
|
);
|
||||||
if (this.#contextPromise === null)
|
}
|
||||||
|
if (this.#contextPromise === null) {
|
||||||
throw new Error(`Execution content promise is missing`);
|
throw new Error(`Execution content promise is missing`);
|
||||||
|
}
|
||||||
return this.#contextPromise;
|
return this.#contextPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +217,9 @@ export class DOMWorld {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async _document(): Promise<ElementHandle> {
|
async _document(): Promise<ElementHandle> {
|
||||||
if (this.#documentPromise) return this.#documentPromise;
|
if (this.#documentPromise) {
|
||||||
|
return this.#documentPromise;
|
||||||
|
}
|
||||||
this.#documentPromise = this.executionContext().then(async (context) => {
|
this.#documentPromise = this.executionContext().then(async (context) => {
|
||||||
const document = await context.evaluateHandle('document');
|
const document = await context.evaluateHandle('document');
|
||||||
const element = document.asElement();
|
const element = document.asElement();
|
||||||
@ -270,10 +277,12 @@ export class DOMWorld {
|
|||||||
async content(): Promise<string> {
|
async content(): Promise<string> {
|
||||||
return await this.evaluate(() => {
|
return await this.evaluate(() => {
|
||||||
let retVal = '';
|
let retVal = '';
|
||||||
if (document.doctype)
|
if (document.doctype) {
|
||||||
retVal = new XMLSerializer().serializeToString(document.doctype);
|
retVal = new XMLSerializer().serializeToString(document.doctype);
|
||||||
if (document.documentElement)
|
}
|
||||||
|
if (document.documentElement) {
|
||||||
retVal += document.documentElement.outerHTML;
|
retVal += document.documentElement.outerHTML;
|
||||||
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -307,7 +316,9 @@ export class DOMWorld {
|
|||||||
watcher.lifecyclePromise(),
|
watcher.lifecyclePromise(),
|
||||||
]);
|
]);
|
||||||
watcher.dispose();
|
watcher.dispose();
|
||||||
if (error) throw error;
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -406,8 +417,12 @@ export class DOMWorld {
|
|||||||
): Promise<HTMLElement> {
|
): Promise<HTMLElement> {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = url;
|
script.src = url;
|
||||||
if (id) script.id = id;
|
if (id) {
|
||||||
if (type) script.type = type;
|
script.id = id;
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
script.type = type;
|
||||||
|
}
|
||||||
const promise = new Promise((res, rej) => {
|
const promise = new Promise((res, rej) => {
|
||||||
script.onload = res;
|
script.onload = res;
|
||||||
script.onerror = rej;
|
script.onerror = rej;
|
||||||
@ -425,11 +440,15 @@ export class DOMWorld {
|
|||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.type = type;
|
script.type = type;
|
||||||
script.text = content;
|
script.text = content;
|
||||||
if (id) script.id = id;
|
if (id) {
|
||||||
|
script.id = id;
|
||||||
|
}
|
||||||
let error = null;
|
let error = null;
|
||||||
script.onerror = (e) => (error = e);
|
script.onerror = (e) => (error = e);
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
if (error) throw error;
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -656,7 +675,9 @@ export class DOMWorld {
|
|||||||
event: Protocol.Runtime.BindingCalledEvent
|
event: Protocol.Runtime.BindingCalledEvent
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
let payload: { type: string; name: string; seq: number; args: unknown[] };
|
let payload: { type: string; name: string; seq: number; args: unknown[] };
|
||||||
if (!this._hasContext()) return;
|
if (!this._hasContext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const context = await this.executionContext();
|
const context = await this.executionContext();
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(event.payload);
|
payload = JSON.parse(event.payload);
|
||||||
@ -671,9 +692,12 @@ export class DOMWorld {
|
|||||||
!this.#ctxBindings.has(
|
!this.#ctxBindings.has(
|
||||||
DOMWorld.#bindingIdentifier(name, context._contextId)
|
DOMWorld.#bindingIdentifier(name, context._contextId)
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
return;
|
return;
|
||||||
if (context._contextId !== event.executionContextId) return;
|
}
|
||||||
|
if (context._contextId !== event.executionContextId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const fn = this._boundFunctions.get(name);
|
const fn = this._boundFunctions.get(name);
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
@ -687,7 +711,9 @@ export class DOMWorld {
|
|||||||
// In both caes, the promises above are rejected with a protocol error.
|
// In both caes, the promises above are rejected with a protocol error.
|
||||||
// We can safely ignores these, as the WaitTask is re-installed in
|
// We can safely ignores these, as the WaitTask is re-installed in
|
||||||
// the next execution context if needed.
|
// the next execution context if needed.
|
||||||
if ((error as Error).message.includes('Protocol error')) return;
|
if ((error as Error).message.includes('Protocol error')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
debugError(error);
|
debugError(error);
|
||||||
}
|
}
|
||||||
function deliverResult(name: string, seq: number, result: unknown): void {
|
function deliverResult(name: string, seq: number, result: unknown): void {
|
||||||
@ -859,20 +885,24 @@ export class WaitTask {
|
|||||||
promise: Promise<JSHandle>;
|
promise: Promise<JSHandle>;
|
||||||
|
|
||||||
constructor(options: WaitTaskOptions) {
|
constructor(options: WaitTaskOptions) {
|
||||||
if (isString(options.polling))
|
if (isString(options.polling)) {
|
||||||
assert(
|
assert(
|
||||||
options.polling === 'raf' || options.polling === 'mutation',
|
options.polling === 'raf' || options.polling === 'mutation',
|
||||||
'Unknown polling option: ' + options.polling
|
'Unknown polling option: ' + options.polling
|
||||||
);
|
);
|
||||||
else if (isNumber(options.polling))
|
} else if (isNumber(options.polling)) {
|
||||||
assert(
|
assert(
|
||||||
options.polling > 0,
|
options.polling > 0,
|
||||||
'Cannot poll with non-positive interval: ' + options.polling
|
'Cannot poll with non-positive interval: ' + options.polling
|
||||||
);
|
);
|
||||||
else throw new Error('Unknown polling options: ' + options.polling);
|
} else {
|
||||||
|
throw new Error('Unknown polling options: ' + options.polling);
|
||||||
|
}
|
||||||
|
|
||||||
function getPredicateBody(predicateBody: Function | string) {
|
function getPredicateBody(predicateBody: Function | string) {
|
||||||
if (isString(predicateBody)) return `return (${predicateBody});`;
|
if (isString(predicateBody)) {
|
||||||
|
return `return (${predicateBody});`;
|
||||||
|
}
|
||||||
return `return (${predicateBody})(...args);`;
|
return `return (${predicateBody})(...args);`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -922,11 +952,15 @@ export class WaitTask {
|
|||||||
let success: JSHandle | null = null;
|
let success: JSHandle | null = null;
|
||||||
let error: Error | null = null;
|
let error: Error | null = null;
|
||||||
const context = await this.#domWorld.executionContext();
|
const context = await this.#domWorld.executionContext();
|
||||||
if (this.#terminated || runCount !== this.#runCount) return;
|
if (this.#terminated || runCount !== this.#runCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.#binding) {
|
if (this.#binding) {
|
||||||
await this.#domWorld._addBindingToContext(context, this.#binding.name);
|
await this.#domWorld._addBindingToContext(context, this.#binding.name);
|
||||||
}
|
}
|
||||||
if (this.#terminated || runCount !== this.#runCount) return;
|
if (this.#terminated || runCount !== this.#runCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
success = await context.evaluateHandle(
|
success = await context.evaluateHandle(
|
||||||
waitForPredicatePageFunction,
|
waitForPredicatePageFunction,
|
||||||
@ -942,7 +976,9 @@ export class WaitTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.#terminated || runCount !== this.#runCount) {
|
if (this.#terminated || runCount !== this.#runCount) {
|
||||||
if (success) await success.dispose();
|
if (success) {
|
||||||
|
await success.dispose();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -953,8 +989,9 @@ export class WaitTask {
|
|||||||
!error &&
|
!error &&
|
||||||
(await this.#domWorld.evaluate((s) => !s, success).catch(() => true))
|
(await this.#domWorld.evaluate((s) => !s, success).catch(() => true))
|
||||||
) {
|
) {
|
||||||
if (!success)
|
if (!success) {
|
||||||
throw new Error('Assertion: result handle is not available');
|
throw new Error('Assertion: result handle is not available');
|
||||||
|
}
|
||||||
await success.dispose();
|
await success.dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -978,17 +1015,21 @@ export class WaitTask {
|
|||||||
|
|
||||||
// When the page is navigated, the promise is rejected.
|
// When the page is navigated, the promise is rejected.
|
||||||
// We will try again in the new execution context.
|
// We will try again in the new execution context.
|
||||||
if (error.message.includes('Execution context was destroyed')) return;
|
if (error.message.includes('Execution context was destroyed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We could have tried to evaluate in a context which was already
|
// We could have tried to evaluate in a context which was already
|
||||||
// destroyed.
|
// destroyed.
|
||||||
if (error.message.includes('Cannot find context with specified id'))
|
if (error.message.includes('Cannot find context with specified id')) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.#reject(error);
|
this.#reject(error);
|
||||||
} else {
|
} else {
|
||||||
if (!success)
|
if (!success) {
|
||||||
throw new Error('Assertion: result handle is not available');
|
throw new Error('Assertion: result handle is not available');
|
||||||
|
}
|
||||||
this.#resolve(success);
|
this.#resolve(success);
|
||||||
}
|
}
|
||||||
this.#cleanup();
|
this.#cleanup();
|
||||||
@ -1011,7 +1052,9 @@ async function waitForPredicatePageFunction(
|
|||||||
root = root || document;
|
root = root || document;
|
||||||
const predicate = new Function('...args', predicateBody);
|
const predicate = new Function('...args', predicateBody);
|
||||||
let timedOut = false;
|
let timedOut = false;
|
||||||
if (timeout) setTimeout(() => (timedOut = true), timeout);
|
if (timeout) {
|
||||||
|
setTimeout(() => (timedOut = true), timeout);
|
||||||
|
}
|
||||||
switch (polling) {
|
switch (polling) {
|
||||||
case 'raf':
|
case 'raf':
|
||||||
return await pollRaf();
|
return await pollRaf();
|
||||||
@ -1025,7 +1068,9 @@ async function waitForPredicatePageFunction(
|
|||||||
const success = predicateAcceptsContextElement
|
const success = predicateAcceptsContextElement
|
||||||
? await predicate(root, ...args)
|
? await predicate(root, ...args)
|
||||||
: await predicate(...args);
|
: await predicate(...args);
|
||||||
if (success) return Promise.resolve(success);
|
if (success) {
|
||||||
|
return Promise.resolve(success);
|
||||||
|
}
|
||||||
|
|
||||||
let fulfill = (_?: unknown) => {};
|
let fulfill = (_?: unknown) => {};
|
||||||
const result = new Promise((x) => (fulfill = x));
|
const result = new Promise((x) => (fulfill = x));
|
||||||
@ -1067,8 +1112,11 @@ async function waitForPredicatePageFunction(
|
|||||||
const success = predicateAcceptsContextElement
|
const success = predicateAcceptsContextElement
|
||||||
? await predicate(root, ...args)
|
? await predicate(root, ...args)
|
||||||
: await predicate(...args);
|
: await predicate(...args);
|
||||||
if (success) fulfill(success);
|
if (success) {
|
||||||
else requestAnimationFrame(onRaf);
|
fulfill(success);
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(onRaf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1086,8 +1134,11 @@ async function waitForPredicatePageFunction(
|
|||||||
const success = predicateAcceptsContextElement
|
const success = predicateAcceptsContextElement
|
||||||
? await predicate(root, ...args)
|
? await predicate(root, ...args)
|
||||||
: await predicate(...args);
|
: await predicate(...args);
|
||||||
if (success) fulfill(success);
|
if (success) {
|
||||||
else setTimeout(onTimeout, pollInterval);
|
fulfill(success);
|
||||||
|
} else {
|
||||||
|
setTimeout(onTimeout, pollInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,9 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
|
|||||||
|
|
||||||
return (...logArgs: unknown[]): void => {
|
return (...logArgs: unknown[]): void => {
|
||||||
const debugLevel = globalThis.__PUPPETEER_DEBUG;
|
const debugLevel = globalThis.__PUPPETEER_DEBUG;
|
||||||
if (!debugLevel) return;
|
if (!debugLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const everythingShouldBeLogged = debugLevel === '*';
|
const everythingShouldBeLogged = debugLevel === '*';
|
||||||
|
|
||||||
@ -81,7 +83,9 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
|
|||||||
? prefix.startsWith(debugLevel)
|
? prefix.startsWith(debugLevel)
|
||||||
: prefix === debugLevel);
|
: prefix === debugLevel);
|
||||||
|
|
||||||
if (!prefixMatchesDebugLevel) return;
|
if (!prefixMatchesDebugLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`${prefix}:`, ...logArgs);
|
console.log(`${prefix}:`, ...logArgs);
|
||||||
|
@ -218,20 +218,22 @@ export class ExecutionContext {
|
|||||||
})
|
})
|
||||||
.catch(rewriteError);
|
.catch(rewriteError);
|
||||||
|
|
||||||
if (exceptionDetails)
|
if (exceptionDetails) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Evaluation failed: ' + getExceptionMessage(exceptionDetails)
|
'Evaluation failed: ' + getExceptionMessage(exceptionDetails)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return returnByValue
|
return returnByValue
|
||||||
? valueFromRemoteObject(remoteObject)
|
? valueFromRemoteObject(remoteObject)
|
||||||
: _createJSHandle(this, remoteObject);
|
: _createJSHandle(this, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof pageFunction !== 'function')
|
if (typeof pageFunction !== 'function') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`
|
`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let functionText = pageFunction.toString();
|
let functionText = pageFunction.toString();
|
||||||
try {
|
try {
|
||||||
@ -239,10 +241,12 @@ export class ExecutionContext {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// This means we might have a function shorthand. Try another
|
// This means we might have a function shorthand. Try another
|
||||||
// time prefixing 'function '.
|
// time prefixing 'function '.
|
||||||
if (functionText.startsWith('async '))
|
if (functionText.startsWith('async ')) {
|
||||||
functionText =
|
functionText =
|
||||||
'async function ' + functionText.substring('async '.length);
|
'async function ' + functionText.substring('async '.length);
|
||||||
else functionText = 'function ' + functionText;
|
} else {
|
||||||
|
functionText = 'function ' + functionText;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
new Function('(' + functionText + ')');
|
new Function('(' + functionText + ')');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -271,10 +275,11 @@ export class ExecutionContext {
|
|||||||
}
|
}
|
||||||
const { exceptionDetails, result: remoteObject } =
|
const { exceptionDetails, result: remoteObject } =
|
||||||
await callFunctionOnPromise.catch(rewriteError);
|
await callFunctionOnPromise.catch(rewriteError);
|
||||||
if (exceptionDetails)
|
if (exceptionDetails) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Evaluation failed: ' + getExceptionMessage(exceptionDetails)
|
'Evaluation failed: ' + getExceptionMessage(exceptionDetails)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return returnByValue
|
return returnByValue
|
||||||
? valueFromRemoteObject(remoteObject)
|
? valueFromRemoteObject(remoteObject)
|
||||||
: _createJSHandle(this, remoteObject);
|
: _createJSHandle(this, remoteObject);
|
||||||
@ -283,45 +288,61 @@ export class ExecutionContext {
|
|||||||
this: ExecutionContext,
|
this: ExecutionContext,
|
||||||
arg: unknown
|
arg: unknown
|
||||||
): Protocol.Runtime.CallArgument {
|
): Protocol.Runtime.CallArgument {
|
||||||
if (typeof arg === 'bigint')
|
if (typeof arg === 'bigint') {
|
||||||
// eslint-disable-line valid-typeof
|
// eslint-disable-line valid-typeof
|
||||||
return { unserializableValue: `${arg.toString()}n` };
|
return { unserializableValue: `${arg.toString()}n` };
|
||||||
if (Object.is(arg, -0)) return { unserializableValue: '-0' };
|
}
|
||||||
if (Object.is(arg, Infinity)) return { unserializableValue: 'Infinity' };
|
if (Object.is(arg, -0)) {
|
||||||
if (Object.is(arg, -Infinity))
|
return { unserializableValue: '-0' };
|
||||||
|
}
|
||||||
|
if (Object.is(arg, Infinity)) {
|
||||||
|
return { unserializableValue: 'Infinity' };
|
||||||
|
}
|
||||||
|
if (Object.is(arg, -Infinity)) {
|
||||||
return { unserializableValue: '-Infinity' };
|
return { unserializableValue: '-Infinity' };
|
||||||
if (Object.is(arg, NaN)) return { unserializableValue: 'NaN' };
|
}
|
||||||
|
if (Object.is(arg, NaN)) {
|
||||||
|
return { unserializableValue: 'NaN' };
|
||||||
|
}
|
||||||
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
||||||
if (objectHandle) {
|
if (objectHandle) {
|
||||||
if (objectHandle._context !== this)
|
if (objectHandle._context !== this) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'JSHandles can be evaluated only in the context they were created!'
|
'JSHandles can be evaluated only in the context they were created!'
|
||||||
);
|
);
|
||||||
if (objectHandle._disposed) throw new Error('JSHandle is disposed!');
|
}
|
||||||
if (objectHandle._remoteObject.unserializableValue)
|
if (objectHandle._disposed) {
|
||||||
|
throw new Error('JSHandle is disposed!');
|
||||||
|
}
|
||||||
|
if (objectHandle._remoteObject.unserializableValue) {
|
||||||
return {
|
return {
|
||||||
unserializableValue: objectHandle._remoteObject.unserializableValue,
|
unserializableValue: objectHandle._remoteObject.unserializableValue,
|
||||||
};
|
};
|
||||||
if (!objectHandle._remoteObject.objectId)
|
}
|
||||||
|
if (!objectHandle._remoteObject.objectId) {
|
||||||
return { value: objectHandle._remoteObject.value };
|
return { value: objectHandle._remoteObject.value };
|
||||||
|
}
|
||||||
return { objectId: objectHandle._remoteObject.objectId };
|
return { objectId: objectHandle._remoteObject.objectId };
|
||||||
}
|
}
|
||||||
return { value: arg };
|
return { value: arg };
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteError(error: Error): Protocol.Runtime.EvaluateResponse {
|
function rewriteError(error: Error): Protocol.Runtime.EvaluateResponse {
|
||||||
if (error.message.includes('Object reference chain is too long'))
|
if (error.message.includes('Object reference chain is too long')) {
|
||||||
return { result: { type: 'undefined' } };
|
return { result: { type: 'undefined' } };
|
||||||
if (error.message.includes("Object couldn't be returned by value"))
|
}
|
||||||
|
if (error.message.includes("Object couldn't be returned by value")) {
|
||||||
return { result: { type: 'undefined' } };
|
return { result: { type: 'undefined' } };
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
error.message.endsWith('Cannot find context with specified id') ||
|
error.message.endsWith('Cannot find context with specified id') ||
|
||||||
error.message.endsWith('Inspected target navigated or closed')
|
error.message.endsWith('Inspected target navigated or closed')
|
||||||
)
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Execution context was destroyed, most likely because of a navigation.'
|
'Execution context was destroyed, most likely because of a navigation.'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
watcher.dispose();
|
watcher.dispose();
|
||||||
if (error) throw error;
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
return await watcher.navigationResponse();
|
return await watcher.navigationResponse();
|
||||||
|
|
||||||
async function navigate(
|
async function navigate(
|
||||||
@ -267,7 +269,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
watcher.newDocumentNavigationPromise(),
|
watcher.newDocumentNavigationPromise(),
|
||||||
]);
|
]);
|
||||||
watcher.dispose();
|
watcher.dispose();
|
||||||
if (error) throw error;
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
return await watcher.navigationResponse();
|
return await watcher.navigationResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,13 +285,17 @@ export class FrameManager extends EventEmitter {
|
|||||||
assert(connection);
|
assert(connection);
|
||||||
const session = connection.session(event.sessionId);
|
const session = connection.session(event.sessionId);
|
||||||
assert(session);
|
assert(session);
|
||||||
if (frame) frame._updateClient(session);
|
if (frame) {
|
||||||
|
frame._updateClient(session);
|
||||||
|
}
|
||||||
this.setupEventListeners(session);
|
this.setupEventListeners(session);
|
||||||
await this.initialize(session);
|
await this.initialize(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #onDetachedFromTarget(event: Protocol.Target.DetachedFromTargetEvent) {
|
async #onDetachedFromTarget(event: Protocol.Target.DetachedFromTargetEvent) {
|
||||||
if (!event.targetId) return;
|
if (!event.targetId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const frame = this.#frames.get(event.targetId);
|
const frame = this.#frames.get(event.targetId);
|
||||||
if (frame && frame.isOOPFrame()) {
|
if (frame && frame.isOOPFrame()) {
|
||||||
// When an OOP iframe is removed from the page, it
|
// When an OOP iframe is removed from the page, it
|
||||||
@ -298,20 +306,26 @@ export class FrameManager extends EventEmitter {
|
|||||||
|
|
||||||
#onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
|
#onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
|
||||||
const frame = this.#frames.get(event.frameId);
|
const frame = this.#frames.get(event.frameId);
|
||||||
if (!frame) return;
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
frame._onLifecycleEvent(event.loaderId, event.name);
|
frame._onLifecycleEvent(event.loaderId, event.name);
|
||||||
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
#onFrameStartedLoading(frameId: string): void {
|
#onFrameStartedLoading(frameId: string): void {
|
||||||
const frame = this.#frames.get(frameId);
|
const frame = this.#frames.get(frameId);
|
||||||
if (!frame) return;
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
frame._onLoadingStarted();
|
frame._onLoadingStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
#onFrameStoppedLoading(frameId: string): void {
|
#onFrameStoppedLoading(frameId: string): void {
|
||||||
const frame = this.#frames.get(frameId);
|
const frame = this.#frames.get(frameId);
|
||||||
if (!frame) return;
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
frame._onLoadingStopped();
|
frame._onLoadingStopped();
|
||||||
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
||||||
}
|
}
|
||||||
@ -328,7 +342,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.#onFrameNavigated(frameTree.frame);
|
this.#onFrameNavigated(frameTree.frame);
|
||||||
if (!frameTree.childFrames) return;
|
if (!frameTree.childFrames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const child of frameTree.childFrames) {
|
for (const child of frameTree.childFrames) {
|
||||||
this.#handleFrameTree(session, child);
|
this.#handleFrameTree(session, child);
|
||||||
@ -387,8 +403,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
|
|
||||||
// Detach all child frames first.
|
// Detach all child frames first.
|
||||||
if (frame) {
|
if (frame) {
|
||||||
for (const child of frame.childFrames())
|
for (const child of frame.childFrames()) {
|
||||||
this.#removeFramesRecursively(child);
|
this.#removeFramesRecursively(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update or create main frame.
|
// Update or create main frame.
|
||||||
@ -414,7 +431,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
|
|
||||||
async _ensureIsolatedWorld(session: CDPSession, name: string): Promise<void> {
|
async _ensureIsolatedWorld(session: CDPSession, name: string): Promise<void> {
|
||||||
const key = `${session.id()}:${name}`;
|
const key = `${session.id()}:${name}`;
|
||||||
if (this.#isolatedWorlds.has(key)) return;
|
if (this.#isolatedWorlds.has(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#isolatedWorlds.add(key);
|
this.#isolatedWorlds.add(key);
|
||||||
|
|
||||||
await session.send('Page.addScriptToEvaluateOnNewDocument', {
|
await session.send('Page.addScriptToEvaluateOnNewDocument', {
|
||||||
@ -439,7 +458,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
|
|
||||||
#onFrameNavigatedWithinDocument(frameId: string, url: string): void {
|
#onFrameNavigatedWithinDocument(frameId: string, url: string): void {
|
||||||
const frame = this.#frames.get(frameId);
|
const frame = this.#frames.get(frameId);
|
||||||
if (!frame) return;
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
frame._navigatedWithinDocument(url);
|
frame._navigatedWithinDocument(url);
|
||||||
this.emit(FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame);
|
this.emit(FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame);
|
||||||
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
||||||
@ -454,7 +475,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
// Only remove the frame if the reason for the detached event is
|
// Only remove the frame if the reason for the detached event is
|
||||||
// an actual removement of the frame.
|
// an actual removement of the frame.
|
||||||
// For frames that become OOP iframes, the reason would be 'swap'.
|
// For frames that become OOP iframes, the reason would be 'swap'.
|
||||||
if (frame) this.#removeFramesRecursively(frame);
|
if (frame) {
|
||||||
|
this.#removeFramesRecursively(frame);
|
||||||
|
}
|
||||||
} else if (reason === 'swap') {
|
} else if (reason === 'swap') {
|
||||||
this.emit(FrameManagerEmittedEvents.FrameSwapped, frame);
|
this.emit(FrameManagerEmittedEvents.FrameSwapped, frame);
|
||||||
}
|
}
|
||||||
@ -471,7 +494,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
let world: DOMWorld | undefined;
|
let world: DOMWorld | undefined;
|
||||||
if (frame) {
|
if (frame) {
|
||||||
// Only care about execution contexts created for the current session.
|
// Only care about execution contexts created for the current session.
|
||||||
if (frame._client() !== session) return;
|
if (frame._client() !== session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) {
|
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) {
|
||||||
world = frame._mainWorld;
|
world = frame._mainWorld;
|
||||||
@ -490,7 +515,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
contextPayload,
|
contextPayload,
|
||||||
world
|
world
|
||||||
);
|
);
|
||||||
if (world) world._setContext(context);
|
if (world) {
|
||||||
|
world._setContext(context);
|
||||||
|
}
|
||||||
const key = `${session.id()}:${contextPayload.id}`;
|
const key = `${session.id()}:${contextPayload.id}`;
|
||||||
this.#contextIdToContext.set(key, context);
|
this.#contextIdToContext.set(key, context);
|
||||||
}
|
}
|
||||||
@ -501,17 +528,25 @@ export class FrameManager extends EventEmitter {
|
|||||||
): void {
|
): void {
|
||||||
const key = `${session.id()}:${executionContextId}`;
|
const key = `${session.id()}:${executionContextId}`;
|
||||||
const context = this.#contextIdToContext.get(key);
|
const context = this.#contextIdToContext.get(key);
|
||||||
if (!context) return;
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#contextIdToContext.delete(key);
|
this.#contextIdToContext.delete(key);
|
||||||
if (context._world) context._world._setContext(null);
|
if (context._world) {
|
||||||
|
context._world._setContext(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onExecutionContextsCleared(session: CDPSession): void {
|
#onExecutionContextsCleared(session: CDPSession): void {
|
||||||
for (const [key, context] of this.#contextIdToContext.entries()) {
|
for (const [key, context] of this.#contextIdToContext.entries()) {
|
||||||
// Make sure to only clear execution contexts that belong
|
// Make sure to only clear execution contexts that belong
|
||||||
// to the current session.
|
// to the current session.
|
||||||
if (context._client !== session) continue;
|
if (context._client !== session) {
|
||||||
if (context._world) context._world._setContext(null);
|
continue;
|
||||||
|
}
|
||||||
|
if (context._world) {
|
||||||
|
context._world._setContext(null);
|
||||||
|
}
|
||||||
this.#contextIdToContext.delete(key);
|
this.#contextIdToContext.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -527,8 +562,9 @@ export class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#removeFramesRecursively(frame: Frame): void {
|
#removeFramesRecursively(frame: Frame): void {
|
||||||
for (const child of frame.childFrames())
|
for (const child of frame.childFrames()) {
|
||||||
this.#removeFramesRecursively(child);
|
this.#removeFramesRecursively(child);
|
||||||
|
}
|
||||||
frame._detach();
|
frame._detach();
|
||||||
this.#frames.delete(frame._id);
|
this.#frames.delete(frame._id);
|
||||||
this.emit(FrameManagerEmittedEvents.FrameDetached, frame);
|
this.emit(FrameManagerEmittedEvents.FrameDetached, frame);
|
||||||
@ -716,7 +752,9 @@ export class Frame {
|
|||||||
this._loaderId = '';
|
this._loaderId = '';
|
||||||
|
|
||||||
this._childFrames = new Set();
|
this._childFrames = new Set();
|
||||||
if (this.#parentFrame) this.#parentFrame._childFrames.add(this);
|
if (this.#parentFrame) {
|
||||||
|
this.#parentFrame._childFrames.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
this._updateClient(client);
|
this._updateClient(client);
|
||||||
}
|
}
|
||||||
@ -1237,19 +1275,23 @@ export class Frame {
|
|||||||
|
|
||||||
if (isString(selectorOrFunctionOrTimeout)) {
|
if (isString(selectorOrFunctionOrTimeout)) {
|
||||||
const string = selectorOrFunctionOrTimeout;
|
const string = selectorOrFunctionOrTimeout;
|
||||||
if (xPathPattern.test(string)) return this.waitForXPath(string, options);
|
if (xPathPattern.test(string)) {
|
||||||
|
return this.waitForXPath(string, options);
|
||||||
|
}
|
||||||
return this.waitForSelector(string, options);
|
return this.waitForSelector(string, options);
|
||||||
}
|
}
|
||||||
if (isNumber(selectorOrFunctionOrTimeout))
|
if (isNumber(selectorOrFunctionOrTimeout)) {
|
||||||
return new Promise((fulfill) =>
|
return new Promise((fulfill) =>
|
||||||
setTimeout(fulfill, selectorOrFunctionOrTimeout)
|
setTimeout(fulfill, selectorOrFunctionOrTimeout)
|
||||||
);
|
);
|
||||||
if (typeof selectorOrFunctionOrTimeout === 'function')
|
}
|
||||||
|
if (typeof selectorOrFunctionOrTimeout === 'function') {
|
||||||
return this.waitForFunction(
|
return this.waitForFunction(
|
||||||
selectorOrFunctionOrTimeout,
|
selectorOrFunctionOrTimeout,
|
||||||
options,
|
options,
|
||||||
...args
|
...args
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(
|
new Error(
|
||||||
'Unsupported target type: ' + typeof selectorOrFunctionOrTimeout
|
'Unsupported target type: ' + typeof selectorOrFunctionOrTimeout
|
||||||
@ -1324,7 +1366,9 @@ export class Frame {
|
|||||||
selector,
|
selector,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
if (!handle) return null;
|
if (!handle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const mainExecutionContext = await this._mainWorld.executionContext();
|
const mainExecutionContext = await this._mainWorld.executionContext();
|
||||||
const result = await mainExecutionContext._adoptElementHandle(handle);
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
@ -1351,7 +1395,9 @@ export class Frame {
|
|||||||
options: WaitForSelectorOptions = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle | null> {
|
): Promise<ElementHandle | null> {
|
||||||
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
|
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
|
||||||
if (!handle) return null;
|
if (!handle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const mainExecutionContext = await this._mainWorld.executionContext();
|
const mainExecutionContext = await this._mainWorld.executionContext();
|
||||||
const result = await mainExecutionContext._adoptElementHandle(handle);
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
@ -1456,7 +1502,9 @@ export class Frame {
|
|||||||
this.#detached = true;
|
this.#detached = true;
|
||||||
this._mainWorld._detach();
|
this._mainWorld._detach();
|
||||||
this._secondaryWorld._detach();
|
this._secondaryWorld._detach();
|
||||||
if (this.#parentFrame) this.#parentFrame._childFrames.delete(this);
|
if (this.#parentFrame) {
|
||||||
|
this.#parentFrame._childFrames.delete(this);
|
||||||
|
}
|
||||||
this.#parentFrame = null;
|
this.#parentFrame = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,8 +184,9 @@ export class HTTPRequest {
|
|||||||
this.#interceptHandlers = [];
|
this.#interceptHandlers = [];
|
||||||
this.#initiator = event.initiator;
|
this.#initiator = event.initiator;
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(event.request.headers))
|
for (const [key, value] of Object.entries(event.request.headers)) {
|
||||||
this.#headers[key.toLowerCase()] = value;
|
this.#headers[key.toLowerCase()] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,10 +235,12 @@ export class HTTPRequest {
|
|||||||
* `disabled`, `none`, or `already-handled`.
|
* `disabled`, `none`, or `already-handled`.
|
||||||
*/
|
*/
|
||||||
interceptResolutionState(): InterceptResolutionState {
|
interceptResolutionState(): InterceptResolutionState {
|
||||||
if (!this.#allowInterception)
|
if (!this.#allowInterception) {
|
||||||
return { action: InterceptResolutionAction.Disabled };
|
return { action: InterceptResolutionAction.Disabled };
|
||||||
if (this.#interceptionHandled)
|
}
|
||||||
|
if (this.#interceptionHandled) {
|
||||||
return { action: InterceptResolutionAction.AlreadyHandled };
|
return { action: InterceptResolutionAction.AlreadyHandled };
|
||||||
|
}
|
||||||
return { ...this.#interceptResolutionState };
|
return { ...this.#interceptResolutionState };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +399,9 @@ export class HTTPRequest {
|
|||||||
* failure text if the request fails.
|
* failure text if the request fails.
|
||||||
*/
|
*/
|
||||||
failure(): { errorText: string } | null {
|
failure(): { errorText: string } | null {
|
||||||
if (!this._failureText) return null;
|
if (!this._failureText) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
errorText: this._failureText,
|
errorText: this._failureText,
|
||||||
};
|
};
|
||||||
@ -435,7 +440,9 @@ export class HTTPRequest {
|
|||||||
priority?: number
|
priority?: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Request interception is not supported for data: urls.
|
// Request interception is not supported for data: urls.
|
||||||
if (this.#url.startsWith('data:')) return;
|
if (this.#url.startsWith('data:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
||||||
assert(!this.#interceptionHandled, 'Request is already handled!');
|
assert(!this.#interceptionHandled, 'Request is already handled!');
|
||||||
if (priority === undefined) {
|
if (priority === undefined) {
|
||||||
@ -473,10 +480,11 @@ export class HTTPRequest {
|
|||||||
? Buffer.from(postData).toString('base64')
|
? Buffer.from(postData).toString('base64')
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (this._interceptionId === undefined)
|
if (this._interceptionId === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'HTTPRequest is missing _interceptionId needed for Fetch.continueRequest'
|
'HTTPRequest is missing _interceptionId needed for Fetch.continueRequest'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await this.#client
|
await this.#client
|
||||||
.send('Fetch.continueRequest', {
|
.send('Fetch.continueRequest', {
|
||||||
requestId: this._interceptionId,
|
requestId: this._interceptionId,
|
||||||
@ -527,7 +535,9 @@ export class HTTPRequest {
|
|||||||
priority?: number
|
priority?: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Mocking responses for dataURL requests is not currently supported.
|
// Mocking responses for dataURL requests is not currently supported.
|
||||||
if (this.#url.startsWith('data:')) return;
|
if (this.#url.startsWith('data:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
||||||
assert(!this.#interceptionHandled, 'Request is already handled!');
|
assert(!this.#interceptionHandled, 'Request is already handled!');
|
||||||
if (priority === undefined) {
|
if (priority === undefined) {
|
||||||
@ -570,18 +580,21 @@ export class HTTPRequest {
|
|||||||
: String(value);
|
: String(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response.contentType)
|
if (response.contentType) {
|
||||||
responseHeaders['content-type'] = response.contentType;
|
responseHeaders['content-type'] = response.contentType;
|
||||||
if (responseBody && !('content-length' in responseHeaders))
|
}
|
||||||
|
if (responseBody && !('content-length' in responseHeaders)) {
|
||||||
responseHeaders['content-length'] = String(
|
responseHeaders['content-length'] = String(
|
||||||
Buffer.byteLength(responseBody)
|
Buffer.byteLength(responseBody)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const status = response.status || 200;
|
const status = response.status || 200;
|
||||||
if (this._interceptionId === undefined)
|
if (this._interceptionId === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest'
|
'HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await this.#client
|
await this.#client
|
||||||
.send('Fetch.fulfillRequest', {
|
.send('Fetch.fulfillRequest', {
|
||||||
requestId: this._interceptionId,
|
requestId: this._interceptionId,
|
||||||
@ -614,7 +627,9 @@ export class HTTPRequest {
|
|||||||
priority?: number
|
priority?: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Request interception is not supported for data: urls.
|
// Request interception is not supported for data: urls.
|
||||||
if (this.#url.startsWith('data:')) return;
|
if (this.#url.startsWith('data:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const errorReason = errorReasons[errorCode];
|
const errorReason = errorReasons[errorCode];
|
||||||
assert(errorReason, 'Unknown error code: ' + errorCode);
|
assert(errorReason, 'Unknown error code: ' + errorCode);
|
||||||
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
assert(this.#allowInterception, 'Request Interception is not enabled!');
|
||||||
@ -639,10 +654,11 @@ export class HTTPRequest {
|
|||||||
errorReason: Protocol.Network.ErrorReason | null
|
errorReason: Protocol.Network.ErrorReason | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.#interceptionHandled = true;
|
this.#interceptionHandled = true;
|
||||||
if (this._interceptionId === undefined)
|
if (this._interceptionId === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'HTTPRequest is missing _interceptionId needed for Fetch.failRequest'
|
'HTTPRequest is missing _interceptionId needed for Fetch.failRequest'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await this.#client
|
await this.#client
|
||||||
.send('Fetch.failRequest', {
|
.send('Fetch.failRequest', {
|
||||||
requestId: this._interceptionId,
|
requestId: this._interceptionId,
|
||||||
|
@ -101,13 +101,21 @@ export class HTTPResponse {
|
|||||||
#parseStatusTextFromExtrInfo(
|
#parseStatusTextFromExtrInfo(
|
||||||
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
|
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (!extraInfo || !extraInfo.headersText) return;
|
if (!extraInfo || !extraInfo.headersText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const firstLine = extraInfo.headersText.split('\r', 1)[0];
|
const firstLine = extraInfo.headersText.split('\r', 1)[0];
|
||||||
if (!firstLine) return;
|
if (!firstLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const match = firstLine.match(/[^ ]* [^ ]* (.*)/);
|
const match = firstLine.match(/[^ ]* [^ ]* (.*)/);
|
||||||
if (!match) return;
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const statusText = match[1];
|
const statusText = match[1];
|
||||||
if (!statusText) return;
|
if (!statusText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return statusText;
|
return statusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +196,9 @@ export class HTTPResponse {
|
|||||||
buffer(): Promise<Buffer> {
|
buffer(): Promise<Buffer> {
|
||||||
if (!this.#contentPromise) {
|
if (!this.#contentPromise) {
|
||||||
this.#contentPromise = this.#bodyLoadedPromise.then(async (error) => {
|
this.#contentPromise = this.#bodyLoadedPromise.then(async (error) => {
|
||||||
if (error) throw error;
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await this.#client.send('Network.getResponseBody', {
|
const response = await this.#client.send('Network.getResponseBody', {
|
||||||
requestId: this.#request._requestId,
|
requestId: this.#request._requestId,
|
||||||
|
@ -134,10 +134,18 @@ export class Keyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#modifierBit(key: string): number {
|
#modifierBit(key: string): number {
|
||||||
if (key === 'Alt') return 1;
|
if (key === 'Alt') {
|
||||||
if (key === 'Control') return 2;
|
return 1;
|
||||||
if (key === 'Meta') return 4;
|
}
|
||||||
if (key === 'Shift') return 8;
|
if (key === 'Control') {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (key === 'Meta') {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (key === 'Shift') {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,24 +162,43 @@ export class Keyboard {
|
|||||||
const definition = _keyDefinitions[keyString];
|
const definition = _keyDefinitions[keyString];
|
||||||
assert(definition, `Unknown key: "${keyString}"`);
|
assert(definition, `Unknown key: "${keyString}"`);
|
||||||
|
|
||||||
if (definition.key) description.key = definition.key;
|
if (definition.key) {
|
||||||
if (shift && definition.shiftKey) description.key = definition.shiftKey;
|
description.key = definition.key;
|
||||||
|
}
|
||||||
|
if (shift && definition.shiftKey) {
|
||||||
|
description.key = definition.shiftKey;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.keyCode) description.keyCode = definition.keyCode;
|
if (definition.keyCode) {
|
||||||
if (shift && definition.shiftKeyCode)
|
description.keyCode = definition.keyCode;
|
||||||
|
}
|
||||||
|
if (shift && definition.shiftKeyCode) {
|
||||||
description.keyCode = definition.shiftKeyCode;
|
description.keyCode = definition.shiftKeyCode;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.code) description.code = definition.code;
|
if (definition.code) {
|
||||||
|
description.code = definition.code;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.location) description.location = definition.location;
|
if (definition.location) {
|
||||||
|
description.location = definition.location;
|
||||||
|
}
|
||||||
|
|
||||||
if (description.key.length === 1) description.text = description.key;
|
if (description.key.length === 1) {
|
||||||
|
description.text = description.key;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.text) description.text = definition.text;
|
if (definition.text) {
|
||||||
if (shift && definition.shiftText) description.text = definition.shiftText;
|
description.text = definition.text;
|
||||||
|
}
|
||||||
|
if (shift && definition.shiftText) {
|
||||||
|
description.text = definition.shiftText;
|
||||||
|
}
|
||||||
|
|
||||||
// if any modifiers besides shift are pressed, no text should be sent
|
// if any modifiers besides shift are pressed, no text should be sent
|
||||||
if (this._modifiers & ~8) description.text = '';
|
if (this._modifiers & ~8) {
|
||||||
|
description.text = '';
|
||||||
|
}
|
||||||
|
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
@ -249,7 +276,9 @@ export class Keyboard {
|
|||||||
if (this.charIsKey(char)) {
|
if (this.charIsKey(char)) {
|
||||||
await this.press(char, { delay });
|
await this.press(char, { delay });
|
||||||
} else {
|
} else {
|
||||||
if (delay) await new Promise((f) => setTimeout(f, delay));
|
if (delay) {
|
||||||
|
await new Promise((f) => setTimeout(f, delay));
|
||||||
|
}
|
||||||
await this.sendCharacter(char);
|
await this.sendCharacter(char);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,7 +310,9 @@ export class Keyboard {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { delay = null } = options;
|
const { delay = null } = options;
|
||||||
await this.down(key, options);
|
await this.down(key, options);
|
||||||
if (delay) await new Promise((f) => setTimeout(f, options.delay));
|
if (delay) {
|
||||||
|
await new Promise((f) => setTimeout(f, options.delay));
|
||||||
|
}
|
||||||
await this.up(key);
|
await this.up(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,7 +250,9 @@ export class JSHandle<HandleObjectType = unknown> {
|
|||||||
});
|
});
|
||||||
const result = new Map<string, JSHandle>();
|
const result = new Map<string, JSHandle>();
|
||||||
for (const property of response.result) {
|
for (const property of response.result) {
|
||||||
if (!property.enumerable || !property.value) continue;
|
if (!property.enumerable || !property.value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
result.set(property.name, _createJSHandle(this.#context, property.value));
|
result.set(property.name, _createJSHandle(this.#context, property.value));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -294,7 +296,9 @@ export class JSHandle<HandleObjectType = unknown> {
|
|||||||
* successfully disposed of.
|
* successfully disposed of.
|
||||||
*/
|
*/
|
||||||
async dispose(): Promise<void> {
|
async dispose(): Promise<void> {
|
||||||
if (this.#disposed) return;
|
if (this.#disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#disposed = true;
|
this.#disposed = true;
|
||||||
await releaseObject(this.#client, this.#remoteObject);
|
await releaseObject(this.#client, this.#remoteObject);
|
||||||
}
|
}
|
||||||
@ -418,7 +422,9 @@ export class ElementHandle<
|
|||||||
root: adoptedRoot,
|
root: adoptedRoot,
|
||||||
});
|
});
|
||||||
await adoptedRoot.dispose();
|
await adoptedRoot.dispose();
|
||||||
if (!handle) return null;
|
if (!handle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const mainExecutionContext = await frame._mainWorld.executionContext();
|
const mainExecutionContext = await frame._mainWorld.executionContext();
|
||||||
const result = await mainExecutionContext._adoptElementHandle(handle);
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
@ -497,7 +503,9 @@ export class ElementHandle<
|
|||||||
root: adoptedRoot,
|
root: adoptedRoot,
|
||||||
});
|
});
|
||||||
await adoptedRoot.dispose();
|
await adoptedRoot.dispose();
|
||||||
if (!handle) return null;
|
if (!handle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const mainExecutionContext = await frame._mainWorld.executionContext();
|
const mainExecutionContext = await frame._mainWorld.executionContext();
|
||||||
const result = await mainExecutionContext._adoptElementHandle(handle);
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
@ -516,7 +524,9 @@ export class ElementHandle<
|
|||||||
const nodeInfo = await this._client.send('DOM.describeNode', {
|
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||||
objectId: this._remoteObject.objectId,
|
objectId: this._remoteObject.objectId,
|
||||||
});
|
});
|
||||||
if (typeof nodeInfo.node.frameId !== 'string') return null;
|
if (typeof nodeInfo.node.frameId !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this.#frameManager.frame(nodeInfo.node.frameId);
|
return this.#frameManager.frame(nodeInfo.node.frameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,9 +536,12 @@ export class ElementHandle<
|
|||||||
element: Element,
|
element: Element,
|
||||||
pageJavascriptEnabled: boolean
|
pageJavascriptEnabled: boolean
|
||||||
): Promise<string | false> => {
|
): Promise<string | false> => {
|
||||||
if (!element.isConnected) return 'Node is detached from document';
|
if (!element.isConnected) {
|
||||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
return 'Node is detached from document';
|
||||||
|
}
|
||||||
|
if (element.nodeType !== Node.ELEMENT_NODE) {
|
||||||
return 'Node is not of type HTMLElement';
|
return 'Node is not of type HTMLElement';
|
||||||
|
}
|
||||||
// force-scroll if page's javascript is disabled.
|
// force-scroll if page's javascript is disabled.
|
||||||
if (!pageJavascriptEnabled) {
|
if (!pageJavascriptEnabled) {
|
||||||
element.scrollIntoView({
|
element.scrollIntoView({
|
||||||
@ -563,7 +576,9 @@ export class ElementHandle<
|
|||||||
this.#page.isJavaScriptEnabled()
|
this.#page.isJavaScriptEnabled()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) throw new Error(error);
|
if (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getOOPIFOffsets(
|
async #getOOPIFOffsets(
|
||||||
@ -610,8 +625,9 @@ export class ElementHandle<
|
|||||||
.catch(debugError),
|
.catch(debugError),
|
||||||
this.#page._client().send('Page.getLayoutMetrics'),
|
this.#page._client().send('Page.getLayoutMetrics'),
|
||||||
]);
|
]);
|
||||||
if (!result || !result.quads.length)
|
if (!result || !result.quads.length) {
|
||||||
throw new Error('Node is either not clickable or not an HTMLElement');
|
throw new Error('Node is either not clickable or not an HTMLElement');
|
||||||
|
}
|
||||||
// Filter out quads that have too small area to click into.
|
// Filter out quads that have too small area to click into.
|
||||||
// Fallback to `layoutViewport` in case of using Firefox.
|
// Fallback to `layoutViewport` in case of using Firefox.
|
||||||
const { clientWidth, clientHeight } =
|
const { clientWidth, clientHeight } =
|
||||||
@ -624,8 +640,9 @@ export class ElementHandle<
|
|||||||
this.#intersectQuadWithViewport(quad, clientWidth, clientHeight)
|
this.#intersectQuadWithViewport(quad, clientWidth, clientHeight)
|
||||||
)
|
)
|
||||||
.filter((quad) => computeQuadArea(quad) > 1);
|
.filter((quad) => computeQuadArea(quad) > 1);
|
||||||
if (!quads.length)
|
if (!quads.length) {
|
||||||
throw new Error('Node is either not clickable or not an HTMLElement');
|
throw new Error('Node is either not clickable or not an HTMLElement');
|
||||||
|
}
|
||||||
const quad = quads[0]!;
|
const quad = quads[0]!;
|
||||||
if (offset) {
|
if (offset) {
|
||||||
// Return the point of the first quad identified by offset.
|
// Return the point of the first quad identified by offset.
|
||||||
@ -971,7 +988,9 @@ export class ElementHandle<
|
|||||||
async boundingBox(): Promise<BoundingBox | null> {
|
async boundingBox(): Promise<BoundingBox | null> {
|
||||||
const result = await this.#getBoxModel();
|
const result = await this.#getBoxModel();
|
||||||
|
|
||||||
if (!result) return null;
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame);
|
const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame);
|
||||||
const quad = result.model.border;
|
const quad = result.model.border;
|
||||||
@ -994,7 +1013,9 @@ export class ElementHandle<
|
|||||||
async boxModel(): Promise<BoxModel | null> {
|
async boxModel(): Promise<BoxModel | null> {
|
||||||
const result = await this.#getBoxModel();
|
const result = await this.#getBoxModel();
|
||||||
|
|
||||||
if (!result) return null;
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame);
|
const { offsetX, offsetY } = await this.#getOOPIFOffsets(this.#frame);
|
||||||
|
|
||||||
@ -1078,7 +1099,9 @@ export class ElementHandle<
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (needsViewportReset) await this.#page.setViewport(viewport);
|
if (needsViewportReset) {
|
||||||
|
await this.#page.setViewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
@ -1149,10 +1172,11 @@ export class ElementHandle<
|
|||||||
...args: SerializableOrJSHandle[]
|
...args: SerializableOrJSHandle[]
|
||||||
): Promise<WrapElementHandle<ReturnType>> {
|
): Promise<WrapElementHandle<ReturnType>> {
|
||||||
const elementHandle = await this.$(selector);
|
const elementHandle = await this.$(selector);
|
||||||
if (!elementHandle)
|
if (!elementHandle) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error: failed to find element matching selector "${selector}"`
|
`Error: failed to find element matching selector "${selector}"`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const result = await elementHandle.evaluate<
|
const result = await elementHandle.evaluate<
|
||||||
(
|
(
|
||||||
element: Element,
|
element: Element,
|
||||||
@ -1236,7 +1260,9 @@ export class ElementHandle<
|
|||||||
);
|
);
|
||||||
const array = [];
|
const array = [];
|
||||||
let item;
|
let item;
|
||||||
while ((item = iterator.iterateNext())) array.push(item);
|
while ((item = iterator.iterateNext())) {
|
||||||
|
array.push(item);
|
||||||
|
}
|
||||||
return array;
|
return array;
|
||||||
},
|
},
|
||||||
expression
|
expression
|
||||||
@ -1246,7 +1272,9 @@ export class ElementHandle<
|
|||||||
const result = [];
|
const result = [];
|
||||||
for (const property of properties.values()) {
|
for (const property of properties.values()) {
|
||||||
const elementHandle = property.asElement();
|
const elementHandle = property.asElement();
|
||||||
if (elementHandle) result.push(elementHandle);
|
if (elementHandle) {
|
||||||
|
result.push(elementHandle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -106,8 +106,11 @@ export class LifecycleWatcher {
|
|||||||
waitUntil: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[],
|
waitUntil: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[],
|
||||||
timeout: number
|
timeout: number
|
||||||
) {
|
) {
|
||||||
if (Array.isArray(waitUntil)) waitUntil = waitUntil.slice();
|
if (Array.isArray(waitUntil)) {
|
||||||
else if (typeof waitUntil === 'string') waitUntil = [waitUntil];
|
waitUntil = waitUntil.slice();
|
||||||
|
} else if (typeof waitUntil === 'string') {
|
||||||
|
waitUntil = [waitUntil];
|
||||||
|
}
|
||||||
this.#expectedLifecycle = waitUntil.map((value) => {
|
this.#expectedLifecycle = waitUntil.map((value) => {
|
||||||
const protocolEvent = puppeteerToProtocolLifecycle.get(value);
|
const protocolEvent = puppeteerToProtocolLifecycle.get(value);
|
||||||
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
|
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
|
||||||
@ -163,8 +166,9 @@ export class LifecycleWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onRequest(request: HTTPRequest): void {
|
#onRequest(request: HTTPRequest): void {
|
||||||
if (request.frame() !== this.#frame || !request.isNavigationRequest())
|
if (request.frame() !== this.#frame || !request.isNavigationRequest()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
this.#navigationRequest = request;
|
this.#navigationRequest = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +209,9 @@ export class LifecycleWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #createTimeoutPromise(): Promise<TimeoutError | undefined> {
|
async #createTimeoutPromise(): Promise<TimeoutError | undefined> {
|
||||||
if (!this.#timeout) return new Promise(noop);
|
if (!this.#timeout) {
|
||||||
|
return new Promise(noop);
|
||||||
|
}
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
'Navigation timeout of ' + this.#timeout + ' ms exceeded';
|
'Navigation timeout of ' + this.#timeout + ' ms exceeded';
|
||||||
await new Promise(
|
await new Promise(
|
||||||
@ -215,38 +221,50 @@ export class LifecycleWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#navigatedWithinDocument(frame: Frame): void {
|
#navigatedWithinDocument(frame: Frame): void {
|
||||||
if (frame !== this.#frame) return;
|
if (frame !== this.#frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#hasSameDocumentNavigation = true;
|
this.#hasSameDocumentNavigation = true;
|
||||||
this.#checkLifecycleComplete();
|
this.#checkLifecycleComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigated(frame: Frame): void {
|
#navigated(frame: Frame): void {
|
||||||
if (frame !== this.#frame) return;
|
if (frame !== this.#frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#newDocumentNavigation = true;
|
this.#newDocumentNavigation = true;
|
||||||
this.#checkLifecycleComplete();
|
this.#checkLifecycleComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
#frameSwapped(frame: Frame): void {
|
#frameSwapped(frame: Frame): void {
|
||||||
if (frame !== this.#frame) return;
|
if (frame !== this.#frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#swapped = true;
|
this.#swapped = true;
|
||||||
this.#checkLifecycleComplete();
|
this.#checkLifecycleComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkLifecycleComplete(): void {
|
#checkLifecycleComplete(): void {
|
||||||
// We expect navigation to commit.
|
// We expect navigation to commit.
|
||||||
if (!checkLifecycle(this.#frame, this.#expectedLifecycle)) return;
|
if (!checkLifecycle(this.#frame, this.#expectedLifecycle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#lifecycleCallback();
|
this.#lifecycleCallback();
|
||||||
if (this.#hasSameDocumentNavigation)
|
if (this.#hasSameDocumentNavigation) {
|
||||||
this.#sameDocumentNavigationCompleteCallback();
|
this.#sameDocumentNavigationCompleteCallback();
|
||||||
if (this.#swapped || this.#newDocumentNavigation)
|
}
|
||||||
|
if (this.#swapped || this.#newDocumentNavigation) {
|
||||||
this.#newDocumentNavigationCompleteCallback();
|
this.#newDocumentNavigationCompleteCallback();
|
||||||
|
}
|
||||||
|
|
||||||
function checkLifecycle(
|
function checkLifecycle(
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
expectedLifecycle: ProtocolLifeCycleEvent[]
|
expectedLifecycle: ProtocolLifeCycleEvent[]
|
||||||
): boolean {
|
): boolean {
|
||||||
for (const event of expectedLifecycle) {
|
for (const event of expectedLifecycle) {
|
||||||
if (!frame._lifecycleEvents.has(event)) return false;
|
if (!frame._lifecycleEvents.has(event)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const child of frame.childFrames()) {
|
for (const child of frame.childFrames()) {
|
||||||
if (
|
if (
|
||||||
|
@ -133,10 +133,11 @@ export class NetworkManager extends EventEmitter {
|
|||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
await this.#client.send('Network.enable');
|
await this.#client.send('Network.enable');
|
||||||
if (this.#ignoreHTTPSErrors)
|
if (this.#ignoreHTTPSErrors) {
|
||||||
await this.#client.send('Security.setIgnoreCertificateErrors', {
|
await this.#client.send('Security.setIgnoreCertificateErrors', {
|
||||||
ignore: true,
|
ignore: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(credentials?: Credentials): Promise<void> {
|
async authenticate(credentials?: Credentials): Promise<void> {
|
||||||
@ -221,7 +222,9 @@ export class NetworkManager extends EventEmitter {
|
|||||||
|
|
||||||
async #updateProtocolRequestInterception(): Promise<void> {
|
async #updateProtocolRequestInterception(): Promise<void> {
|
||||||
const enabled = this.#userRequestInterceptionEnabled || !!this.#credentials;
|
const enabled = this.#userRequestInterceptionEnabled || !!this.#credentials;
|
||||||
if (enabled === this.#protocolRequestInterceptionEnabled) return;
|
if (enabled === this.#protocolRequestInterceptionEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#protocolRequestInterceptionEnabled = enabled;
|
this.#protocolRequestInterceptionEnabled = enabled;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -420,7 +423,9 @@ export class NetworkManager extends EventEmitter {
|
|||||||
event: Protocol.Network.RequestServedFromCacheEvent
|
event: Protocol.Network.RequestServedFromCacheEvent
|
||||||
): void {
|
): void {
|
||||||
const request = this.#networkEventManager.getRequest(event.requestId);
|
const request = this.#networkEventManager.getRequest(event.requestId);
|
||||||
if (request) request._fromMemoryCache = true;
|
if (request) {
|
||||||
|
request._fromMemoryCache = true;
|
||||||
|
}
|
||||||
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request);
|
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +458,9 @@ export class NetworkManager extends EventEmitter {
|
|||||||
responseReceived.requestId
|
responseReceived.requestId
|
||||||
);
|
);
|
||||||
// FileUpload sends a response without a matching request.
|
// FileUpload sends a response without a matching request.
|
||||||
if (!request) return;
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const extraInfos = this.#networkEventManager.responseExtraInfo(
|
const extraInfos = this.#networkEventManager.responseExtraInfo(
|
||||||
responseReceived.requestId
|
responseReceived.requestId
|
||||||
@ -561,11 +568,15 @@ export class NetworkManager extends EventEmitter {
|
|||||||
const request = this.#networkEventManager.getRequest(event.requestId);
|
const request = this.#networkEventManager.getRequest(event.requestId);
|
||||||
// For certain requestIds we never receive requestWillBeSent event.
|
// For certain requestIds we never receive requestWillBeSent event.
|
||||||
// @see https://crbug.com/750469
|
// @see https://crbug.com/750469
|
||||||
if (!request) return;
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Under certain conditions we never get the Network.responseReceived
|
// Under certain conditions we never get the Network.responseReceived
|
||||||
// event from protocol. @see https://crbug.com/883475
|
// event from protocol. @see https://crbug.com/883475
|
||||||
if (request.response()) request.response()?._resolveBody(null);
|
if (request.response()) {
|
||||||
|
request.response()?._resolveBody(null);
|
||||||
|
}
|
||||||
this.#forgetRequest(request, true);
|
this.#forgetRequest(request, true);
|
||||||
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
|
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
|
||||||
}
|
}
|
||||||
@ -587,10 +598,14 @@ export class NetworkManager extends EventEmitter {
|
|||||||
const request = this.#networkEventManager.getRequest(event.requestId);
|
const request = this.#networkEventManager.getRequest(event.requestId);
|
||||||
// For certain requestIds we never receive requestWillBeSent event.
|
// For certain requestIds we never receive requestWillBeSent event.
|
||||||
// @see https://crbug.com/750469
|
// @see https://crbug.com/750469
|
||||||
if (!request) return;
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
request._failureText = event.errorText;
|
request._failureText = event.errorText;
|
||||||
const response = request.response();
|
const response = request.response();
|
||||||
if (response) response._resolveBody(null);
|
if (response) {
|
||||||
|
response._resolveBody(null);
|
||||||
|
}
|
||||||
this.#forgetRequest(request, true);
|
this.#forgetRequest(request, true);
|
||||||
this.emit(NetworkManagerEmittedEvents.RequestFailed, request);
|
this.emit(NetworkManagerEmittedEvents.RequestFailed, request);
|
||||||
}
|
}
|
||||||
|
@ -451,7 +451,9 @@ export class Page extends EventEmitter {
|
|||||||
screenshotTaskQueue
|
screenshotTaskQueue
|
||||||
);
|
);
|
||||||
await page.#initialize();
|
await page.#initialize();
|
||||||
if (defaultViewport) await page.setViewport(defaultViewport);
|
if (defaultViewport) {
|
||||||
|
await page.setViewport(defaultViewport);
|
||||||
|
}
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +547,9 @@ export class Page extends EventEmitter {
|
|||||||
);
|
);
|
||||||
client.on('Target.detachedFromTarget', (event) => {
|
client.on('Target.detachedFromTarget', (event) => {
|
||||||
const worker = this.#workers.get(event.sessionId);
|
const worker = this.#workers.get(event.sessionId);
|
||||||
if (!worker) return;
|
if (!worker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#workers.delete(event.sessionId);
|
this.#workers.delete(event.sessionId);
|
||||||
this.emit(PageEmittedEvents.WorkerDestroyed, worker);
|
this.emit(PageEmittedEvents.WorkerDestroyed, worker);
|
||||||
});
|
});
|
||||||
@ -615,7 +619,9 @@ export class Page extends EventEmitter {
|
|||||||
async #onFileChooser(
|
async #onFileChooser(
|
||||||
event: Protocol.Page.FileChooserOpenedEvent
|
event: Protocol.Page.FileChooserOpenedEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.#fileChooserInterceptors.size) return;
|
if (!this.#fileChooserInterceptors.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const frame = this.#frameManager.frame(event.frameId);
|
const frame = this.#frameManager.frame(event.frameId);
|
||||||
assert(frame);
|
assert(frame);
|
||||||
const context = await frame.executionContext();
|
const context = await frame.executionContext();
|
||||||
@ -623,7 +629,9 @@ export class Page extends EventEmitter {
|
|||||||
const interceptors = Array.from(this.#fileChooserInterceptors);
|
const interceptors = Array.from(this.#fileChooserInterceptors);
|
||||||
this.#fileChooserInterceptors.clear();
|
this.#fileChooserInterceptors.clear();
|
||||||
const fileChooser = new FileChooser(element, event);
|
const fileChooser = new FileChooser(element, event);
|
||||||
for (const interceptor of interceptors) interceptor.call(null, fileChooser);
|
for (const interceptor of interceptors) {
|
||||||
|
interceptor.call(null, fileChooser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -711,10 +719,11 @@ export class Page extends EventEmitter {
|
|||||||
async waitForFileChooser(
|
async waitForFileChooser(
|
||||||
options: WaitTimeoutOptions = {}
|
options: WaitTimeoutOptions = {}
|
||||||
): Promise<FileChooser> {
|
): Promise<FileChooser> {
|
||||||
if (!this.#fileChooserInterceptors.size)
|
if (!this.#fileChooserInterceptors.size) {
|
||||||
await this.#client.send('Page.setInterceptFileChooserDialog', {
|
await this.#client.send('Page.setInterceptFileChooserDialog', {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { timeout = this.#timeoutSettings.timeout() } = options;
|
const { timeout = this.#timeoutSettings.timeout() } = options;
|
||||||
let callback!: (value: FileChooser | PromiseLike<FileChooser>) => void;
|
let callback!: (value: FileChooser | PromiseLike<FileChooser>) => void;
|
||||||
@ -742,18 +751,21 @@ export class Page extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async setGeolocation(options: GeolocationOptions): Promise<void> {
|
async setGeolocation(options: GeolocationOptions): Promise<void> {
|
||||||
const { longitude, latitude, accuracy = 0 } = options;
|
const { longitude, latitude, accuracy = 0 } = options;
|
||||||
if (longitude < -180 || longitude > 180)
|
if (longitude < -180 || longitude > 180) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`
|
`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`
|
||||||
);
|
);
|
||||||
if (latitude < -90 || latitude > 90)
|
}
|
||||||
|
if (latitude < -90 || latitude > 90) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`
|
`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`
|
||||||
);
|
);
|
||||||
if (accuracy < 0)
|
}
|
||||||
|
if (accuracy < 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`
|
`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await this.#client.send('Emulation.setGeolocationOverride', {
|
await this.#client.send('Emulation.setGeolocationOverride', {
|
||||||
longitude,
|
longitude,
|
||||||
latitude,
|
latitude,
|
||||||
@ -795,12 +807,15 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
#onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
|
#onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
|
||||||
const { level, text, args, source, url, lineNumber } = event.entry;
|
const { level, text, args, source, url, lineNumber } = event.entry;
|
||||||
if (args) args.map((arg) => releaseObject(this.#client, arg));
|
if (args) {
|
||||||
if (source !== 'worker')
|
args.map((arg) => releaseObject(this.#client, arg));
|
||||||
|
}
|
||||||
|
if (source !== 'worker') {
|
||||||
this.emit(
|
this.emit(
|
||||||
PageEmittedEvents.Console,
|
PageEmittedEvents.Console,
|
||||||
new ConsoleMessage(level, text, [], [{ url, lineNumber }])
|
new ConsoleMessage(level, text, [], [{ url, lineNumber }])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1284,7 +1299,9 @@ export class Page extends EventEmitter {
|
|||||||
const pageURL = this.url();
|
const pageURL = this.url();
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
const item = Object.assign({}, cookie);
|
const item = Object.assign({}, cookie);
|
||||||
if (!cookie.url && pageURL.startsWith('http')) item.url = pageURL;
|
if (!cookie.url && pageURL.startsWith('http')) {
|
||||||
|
item.url = pageURL;
|
||||||
|
}
|
||||||
await this.#client.send('Network.deleteCookies', item);
|
await this.#client.send('Network.deleteCookies', item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1300,7 +1317,9 @@ export class Page extends EventEmitter {
|
|||||||
const startsWithHTTP = pageURL.startsWith('http');
|
const startsWithHTTP = pageURL.startsWith('http');
|
||||||
const items = cookies.map((cookie) => {
|
const items = cookies.map((cookie) => {
|
||||||
const item = Object.assign({}, cookie);
|
const item = Object.assign({}, cookie);
|
||||||
if (!item.url && startsWithHTTP) item.url = pageURL;
|
if (!item.url && startsWithHTTP) {
|
||||||
|
item.url = pageURL;
|
||||||
|
}
|
||||||
assert(
|
assert(
|
||||||
item.url !== 'about:blank',
|
item.url !== 'about:blank',
|
||||||
`Blank page can not have cookie "${item.name}"`
|
`Blank page can not have cookie "${item.name}"`
|
||||||
@ -1312,8 +1331,9 @@ export class Page extends EventEmitter {
|
|||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
await this.deleteCookie(...items);
|
await this.deleteCookie(...items);
|
||||||
if (items.length)
|
if (items.length) {
|
||||||
await this.#client.send('Network.setCookies', { cookies: items });
|
await this.#client.send('Network.setCookies', { cookies: items });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1410,10 +1430,11 @@ export class Page extends EventEmitter {
|
|||||||
name: string,
|
name: string,
|
||||||
puppeteerFunction: Function | { default: Function }
|
puppeteerFunction: Function | { default: Function }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.#pageBindings.has(name))
|
if (this.#pageBindings.has(name)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to add page binding with name ${name}: window['${name}'] already exists!`
|
`Failed to add page binding with name ${name}: window['${name}'] already exists!`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let exposedFunction: Function;
|
let exposedFunction: Function;
|
||||||
if (typeof puppeteerFunction === 'function') {
|
if (typeof puppeteerFunction === 'function') {
|
||||||
@ -1580,7 +1601,9 @@ export class Page extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { type, name, seq, args } = payload;
|
const { type, name, seq, args } = payload;
|
||||||
if (type !== 'exposedFun' || !this.#pageBindings.has(name)) return;
|
if (type !== 'exposedFun' || !this.#pageBindings.has(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let expression = null;
|
let expression = null;
|
||||||
try {
|
try {
|
||||||
const pageBinding = this.#pageBindings.get(name);
|
const pageBinding = this.#pageBindings.get(name);
|
||||||
@ -1588,14 +1611,16 @@ export class Page extends EventEmitter {
|
|||||||
const result = await pageBinding(...args);
|
const result = await pageBinding(...args);
|
||||||
expression = pageBindingDeliverResultString(name, seq, result);
|
expression = pageBindingDeliverResultString(name, seq, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isErrorLike(error))
|
if (isErrorLike(error)) {
|
||||||
expression = pageBindingDeliverErrorString(
|
expression = pageBindingDeliverErrorString(
|
||||||
name,
|
name,
|
||||||
seq,
|
seq,
|
||||||
error.message,
|
error.message,
|
||||||
error.stack
|
error.stack
|
||||||
);
|
);
|
||||||
else expression = pageBindingDeliverErrorValueString(name, seq, error);
|
} else {
|
||||||
|
expression = pageBindingDeliverErrorValueString(name, seq, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.#client
|
this.#client
|
||||||
.send('Runtime.evaluate', {
|
.send('Runtime.evaluate', {
|
||||||
@ -1617,8 +1642,11 @@ export class Page extends EventEmitter {
|
|||||||
const textTokens = [];
|
const textTokens = [];
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
const remoteObject = arg._remoteObject;
|
const remoteObject = arg._remoteObject;
|
||||||
if (remoteObject.objectId) textTokens.push(arg.toString());
|
if (remoteObject.objectId) {
|
||||||
else textTokens.push(valueFromRemoteObject(remoteObject));
|
textTokens.push(arg.toString());
|
||||||
|
} else {
|
||||||
|
textTokens.push(valueFromRemoteObject(remoteObject));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const stackTraceLocations = [];
|
const stackTraceLocations = [];
|
||||||
if (stackTrace) {
|
if (stackTrace) {
|
||||||
@ -1853,12 +1881,13 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#sessionClosePromise(): Promise<Error> {
|
#sessionClosePromise(): Promise<Error> {
|
||||||
if (!this.#disconnectPromise)
|
if (!this.#disconnectPromise) {
|
||||||
this.#disconnectPromise = new Promise((fulfill) =>
|
this.#disconnectPromise = new Promise((fulfill) =>
|
||||||
this.#client.once(CDPSessionEmittedEvents.Disconnected, () =>
|
this.#client.once(CDPSessionEmittedEvents.Disconnected, () =>
|
||||||
fulfill(new Error('Target closed'))
|
fulfill(new Error('Target closed'))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return this.#disconnectPromise;
|
return this.#disconnectPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1896,9 +1925,12 @@ export class Page extends EventEmitter {
|
|||||||
this.#frameManager.networkManager(),
|
this.#frameManager.networkManager(),
|
||||||
NetworkManagerEmittedEvents.Request,
|
NetworkManagerEmittedEvents.Request,
|
||||||
(request) => {
|
(request) => {
|
||||||
if (isString(urlOrPredicate)) return urlOrPredicate === request.url();
|
if (isString(urlOrPredicate)) {
|
||||||
if (typeof urlOrPredicate === 'function')
|
return urlOrPredicate === request.url();
|
||||||
|
}
|
||||||
|
if (typeof urlOrPredicate === 'function') {
|
||||||
return !!urlOrPredicate(request);
|
return !!urlOrPredicate(request);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
@ -1942,9 +1974,12 @@ export class Page extends EventEmitter {
|
|||||||
this.#frameManager.networkManager(),
|
this.#frameManager.networkManager(),
|
||||||
NetworkManagerEmittedEvents.Response,
|
NetworkManagerEmittedEvents.Response,
|
||||||
async (response) => {
|
async (response) => {
|
||||||
if (isString(urlOrPredicate)) return urlOrPredicate === response.url();
|
if (isString(urlOrPredicate)) {
|
||||||
if (typeof urlOrPredicate === 'function')
|
return urlOrPredicate === response.url();
|
||||||
|
}
|
||||||
|
if (typeof urlOrPredicate === 'function') {
|
||||||
return !!(await urlOrPredicate(response));
|
return !!(await urlOrPredicate(response));
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
@ -1984,8 +2019,9 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
const evaluate = () => {
|
const evaluate = () => {
|
||||||
idleTimer && clearTimeout(idleTimer);
|
idleTimer && clearTimeout(idleTimer);
|
||||||
if (networkManager.numRequestsInProgress() === 0)
|
if (networkManager.numRequestsInProgress() === 0) {
|
||||||
idleTimer = setTimeout(onIdle, idleTime);
|
idleTimer = setTimeout(onIdle, idleTime);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
evaluate();
|
evaluate();
|
||||||
@ -2152,7 +2188,9 @@ export class Page extends EventEmitter {
|
|||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
const history = await this.#client.send('Page.getNavigationHistory');
|
const history = await this.#client.send('Page.getNavigationHistory');
|
||||||
const entry = history.entries[history.currentIndex + delta];
|
const entry = history.entries[history.currentIndex + delta];
|
||||||
if (!entry) return null;
|
if (!entry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
this.waitForNavigation(options),
|
this.waitForNavigation(options),
|
||||||
this.#client.send('Page.navigateToHistoryEntry', { entryId: entry.id }),
|
this.#client.send('Page.navigateToHistoryEntry', { entryId: entry.id }),
|
||||||
@ -2208,7 +2246,9 @@ export class Page extends EventEmitter {
|
|||||||
* It will take full effect on the next navigation.
|
* It will take full effect on the next navigation.
|
||||||
*/
|
*/
|
||||||
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
|
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
|
||||||
if (this.#javascriptEnabled === enabled) return;
|
if (this.#javascriptEnabled === enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#javascriptEnabled = enabled;
|
this.#javascriptEnabled = enabled;
|
||||||
await this.#client.send('Emulation.setScriptExecutionDisabled', {
|
await this.#client.send('Emulation.setScriptExecutionDisabled', {
|
||||||
value: !enabled,
|
value: !enabled,
|
||||||
@ -2328,7 +2368,9 @@ export class Page extends EventEmitter {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async emulateMediaFeatures(features?: MediaFeature[]): Promise<void> {
|
async emulateMediaFeatures(features?: MediaFeature[]): Promise<void> {
|
||||||
if (!features) await this.#client.send('Emulation.setEmulatedMedia', {});
|
if (!features) {
|
||||||
|
await this.#client.send('Emulation.setEmulatedMedia', {});
|
||||||
|
}
|
||||||
if (Array.isArray(features)) {
|
if (Array.isArray(features)) {
|
||||||
for (const mediaFeature of features) {
|
for (const mediaFeature of features) {
|
||||||
const name = mediaFeature.name;
|
const name = mediaFeature.name;
|
||||||
@ -2491,7 +2533,9 @@ export class Page extends EventEmitter {
|
|||||||
async setViewport(viewport: Viewport): Promise<void> {
|
async setViewport(viewport: Viewport): Promise<void> {
|
||||||
const needsReload = await this.#emulationManager.emulateViewport(viewport);
|
const needsReload = await this.#emulationManager.emulateViewport(viewport);
|
||||||
this.#viewport = viewport;
|
this.#viewport = viewport;
|
||||||
if (needsReload) await this.reload();
|
if (needsReload) {
|
||||||
|
await this.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2813,8 +2857,9 @@ export class Page extends EventEmitter {
|
|||||||
await this.#resetDefaultBackgroundColor();
|
await this.#resetDefaultBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.fullPage && this.#viewport)
|
if (options.fullPage && this.#viewport) {
|
||||||
await this.setViewport(this.#viewport);
|
await this.setViewport(this.#viewport);
|
||||||
|
}
|
||||||
|
|
||||||
const buffer =
|
const buffer =
|
||||||
options.encoding === 'base64'
|
options.encoding === 'base64'
|
||||||
@ -3414,7 +3459,9 @@ const unitToPixels = {
|
|||||||
function convertPrintParameterToInches(
|
function convertPrintParameterToInches(
|
||||||
parameter?: string | number
|
parameter?: string | number
|
||||||
): number | undefined {
|
): number | undefined {
|
||||||
if (typeof parameter === 'undefined') return undefined;
|
if (typeof parameter === 'undefined') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
let pixels;
|
let pixels;
|
||||||
if (isNumber(parameter)) {
|
if (isNumber(parameter)) {
|
||||||
// Treat numbers as pixel values to be aligned with phantom's paperSize.
|
// Treat numbers as pixel values to be aligned with phantom's paperSize.
|
||||||
|
@ -68,7 +68,9 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
internalHandler.queryOne = async (element, selector) => {
|
internalHandler.queryOne = async (element, selector) => {
|
||||||
const jsHandle = await element.evaluateHandle(queryOne, selector);
|
const jsHandle = await element.evaluateHandle(queryOne, selector);
|
||||||
const elementHandle = jsHandle.asElement();
|
const elementHandle = jsHandle.asElement();
|
||||||
if (elementHandle) return elementHandle;
|
if (elementHandle) {
|
||||||
|
return elementHandle;
|
||||||
|
}
|
||||||
await jsHandle.dispose();
|
await jsHandle.dispose();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -88,7 +90,9 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
|||||||
const result = [];
|
const result = [];
|
||||||
for (const property of properties.values()) {
|
for (const property of properties.values()) {
|
||||||
const elementHandle = property.asElement();
|
const elementHandle = property.asElement();
|
||||||
if (elementHandle) result.push(elementHandle);
|
if (elementHandle) {
|
||||||
|
result.push(elementHandle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@ -174,12 +178,14 @@ export function _registerCustomQueryHandler(
|
|||||||
name: string,
|
name: string,
|
||||||
handler: CustomQueryHandler
|
handler: CustomQueryHandler
|
||||||
): void {
|
): void {
|
||||||
if (queryHandlers.get(name))
|
if (queryHandlers.get(name)) {
|
||||||
throw new Error(`A custom query handler named "${name}" already exists`);
|
throw new Error(`A custom query handler named "${name}" already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
const isValidName = /^[a-zA-Z]+$/.test(name);
|
const isValidName = /^[a-zA-Z]+$/.test(name);
|
||||||
if (!isValidName)
|
if (!isValidName) {
|
||||||
throw new Error(`Custom query handler names may only contain [a-zA-Z]`);
|
throw new Error(`Custom query handler names may only contain [a-zA-Z]`);
|
||||||
|
}
|
||||||
|
|
||||||
const internalHandler = makeQueryHandler(handler);
|
const internalHandler = makeQueryHandler(handler);
|
||||||
|
|
||||||
@ -217,17 +223,19 @@ export function _getQueryHandlerAndSelector(selector: string): {
|
|||||||
queryHandler: InternalQueryHandler;
|
queryHandler: InternalQueryHandler;
|
||||||
} {
|
} {
|
||||||
const hasCustomQueryHandler = /^[a-zA-Z]+\//.test(selector);
|
const hasCustomQueryHandler = /^[a-zA-Z]+\//.test(selector);
|
||||||
if (!hasCustomQueryHandler)
|
if (!hasCustomQueryHandler) {
|
||||||
return { updatedSelector: selector, queryHandler: _defaultHandler };
|
return { updatedSelector: selector, queryHandler: _defaultHandler };
|
||||||
|
}
|
||||||
|
|
||||||
const index = selector.indexOf('/');
|
const index = selector.indexOf('/');
|
||||||
const name = selector.slice(0, index);
|
const name = selector.slice(0, index);
|
||||||
const updatedSelector = selector.slice(index + 1);
|
const updatedSelector = selector.slice(index + 1);
|
||||||
const queryHandler = queryHandlers.get(name);
|
const queryHandler = queryHandlers.get(name);
|
||||||
if (!queryHandler)
|
if (!queryHandler) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Query set to use "${name}", but no query handler of that name was found`
|
`Query set to use "${name}", but no query handler of that name was found`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updatedSelector,
|
updatedSelector,
|
||||||
|
@ -87,12 +87,17 @@ export class Target {
|
|||||||
this._initializedPromise = new Promise<boolean>(
|
this._initializedPromise = new Promise<boolean>(
|
||||||
(fulfill) => (this._initializedCallback = fulfill)
|
(fulfill) => (this._initializedCallback = fulfill)
|
||||||
).then(async (success) => {
|
).then(async (success) => {
|
||||||
if (!success) return false;
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const opener = this.opener();
|
const opener = this.opener();
|
||||||
if (!opener || !opener.#pagePromise || this.type() !== 'page')
|
if (!opener || !opener.#pagePromise || this.type() !== 'page') {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
const openerPage = await opener.#pagePromise;
|
const openerPage = await opener.#pagePromise;
|
||||||
if (!openerPage.listenerCount(PageEmittedEvents.Popup)) return true;
|
if (!openerPage.listenerCount(PageEmittedEvents.Popup)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const popupPage = await this.page();
|
const popupPage = await this.page();
|
||||||
openerPage.emit(PageEmittedEvents.Popup, popupPage);
|
openerPage.emit(PageEmittedEvents.Popup, popupPage);
|
||||||
return true;
|
return true;
|
||||||
@ -103,7 +108,9 @@ export class Target {
|
|||||||
this._isInitialized =
|
this._isInitialized =
|
||||||
!this._isPageTargetCallback(this.#targetInfo) ||
|
!this._isPageTargetCallback(this.#targetInfo) ||
|
||||||
this.#targetInfo.url !== '';
|
this.#targetInfo.url !== '';
|
||||||
if (this._isInitialized) this._initializedCallback(true);
|
if (this._isInitialized) {
|
||||||
|
this._initializedCallback(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,8 +152,9 @@ export class Target {
|
|||||||
if (
|
if (
|
||||||
this.#targetInfo.type !== 'service_worker' &&
|
this.#targetInfo.type !== 'service_worker' &&
|
||||||
this.#targetInfo.type !== 'shared_worker'
|
this.#targetInfo.type !== 'shared_worker'
|
||||||
)
|
) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
if (!this.#workerPromise) {
|
if (!this.#workerPromise) {
|
||||||
// TODO(einbinder): Make workers send their console logs.
|
// TODO(einbinder): Make workers send their console logs.
|
||||||
this.#workerPromise = this.#sessionFactory().then(
|
this.#workerPromise = this.#sessionFactory().then(
|
||||||
@ -189,8 +197,9 @@ export class Target {
|
|||||||
type === 'shared_worker' ||
|
type === 'shared_worker' ||
|
||||||
type === 'browser' ||
|
type === 'browser' ||
|
||||||
type === 'webview'
|
type === 'webview'
|
||||||
)
|
) {
|
||||||
return type;
|
return type;
|
||||||
|
}
|
||||||
return 'other';
|
return 'other';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +222,9 @@ export class Target {
|
|||||||
*/
|
*/
|
||||||
opener(): Target | undefined {
|
opener(): Target | undefined {
|
||||||
const { openerId } = this.#targetInfo;
|
const { openerId } = this.#targetInfo;
|
||||||
if (!openerId) return;
|
if (!openerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return this.browser()._targets.get(openerId);
|
return this.browser()._targets.get(openerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,14 +37,19 @@ export class TimeoutSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigationTimeout(): number {
|
navigationTimeout(): number {
|
||||||
if (this.#defaultNavigationTimeout !== null)
|
if (this.#defaultNavigationTimeout !== null) {
|
||||||
return this.#defaultNavigationTimeout;
|
return this.#defaultNavigationTimeout;
|
||||||
if (this.#defaultTimeout !== null) return this.#defaultTimeout;
|
}
|
||||||
|
if (this.#defaultTimeout !== null) {
|
||||||
|
return this.#defaultTimeout;
|
||||||
|
}
|
||||||
return DEFAULT_TIMEOUT;
|
return DEFAULT_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout(): number {
|
timeout(): number {
|
||||||
if (this.#defaultTimeout !== null) return this.#defaultTimeout;
|
if (this.#defaultTimeout !== null) {
|
||||||
|
return this.#defaultTimeout;
|
||||||
|
}
|
||||||
return DEFAULT_TIMEOUT;
|
return DEFAULT_TIMEOUT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,9 @@ export class Tracing {
|
|||||||
categories = defaultCategories,
|
categories = defaultCategories,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (screenshots) categories.push('disabled-by-default-devtools.screenshot');
|
if (screenshots) {
|
||||||
|
categories.push('disabled-by-default-devtools.screenshot');
|
||||||
|
}
|
||||||
|
|
||||||
const excludedCategories = categories
|
const excludedCategories = categories
|
||||||
.filter((cat) => cat.startsWith('-'))
|
.filter((cat) => cat.startsWith('-'))
|
||||||
|
@ -23,12 +23,16 @@ export const assert: (value: unknown, message?: string) => asserts value = (
|
|||||||
value,
|
value,
|
||||||
message
|
message
|
||||||
) => {
|
) => {
|
||||||
if (!value) throw new Error(message);
|
if (!value) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const assertNever: (
|
export const assertNever: (
|
||||||
value: unknown,
|
value: unknown,
|
||||||
message?: string
|
message?: string
|
||||||
) => asserts value is never = (value, message) => {
|
) => asserts value is never = (value, message) => {
|
||||||
if (value) throw new Error(message);
|
if (value) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -28,10 +28,11 @@ export const debugError = debug('puppeteer:error');
|
|||||||
export function getExceptionMessage(
|
export function getExceptionMessage(
|
||||||
exceptionDetails: Protocol.Runtime.ExceptionDetails
|
exceptionDetails: Protocol.Runtime.ExceptionDetails
|
||||||
): string {
|
): string {
|
||||||
if (exceptionDetails.exception)
|
if (exceptionDetails.exception) {
|
||||||
return (
|
return (
|
||||||
exceptionDetails.exception.description || exceptionDetails.exception.value
|
exceptionDetails.exception.description || exceptionDetails.exception.value
|
||||||
);
|
);
|
||||||
|
}
|
||||||
let message = exceptionDetails.text;
|
let message = exceptionDetails.text;
|
||||||
if (exceptionDetails.stackTrace) {
|
if (exceptionDetails.stackTrace) {
|
||||||
for (const callframe of exceptionDetails.stackTrace.callFrames) {
|
for (const callframe of exceptionDetails.stackTrace.callFrames) {
|
||||||
@ -53,8 +54,9 @@ export function valueFromRemoteObject(
|
|||||||
): any {
|
): any {
|
||||||
assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
|
assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
|
||||||
if (remoteObject.unserializableValue) {
|
if (remoteObject.unserializableValue) {
|
||||||
if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined')
|
if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined') {
|
||||||
return BigInt(remoteObject.unserializableValue.replace('n', ''));
|
return BigInt(remoteObject.unserializableValue.replace('n', ''));
|
||||||
|
}
|
||||||
switch (remoteObject.unserializableValue) {
|
switch (remoteObject.unserializableValue) {
|
||||||
case '-0':
|
case '-0':
|
||||||
return -0;
|
return -0;
|
||||||
@ -78,7 +80,9 @@ export async function releaseObject(
|
|||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
remoteObject: Protocol.Runtime.RemoteObject
|
remoteObject: Protocol.Runtime.RemoteObject
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!remoteObject.objectId) return;
|
if (!remoteObject.objectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await client
|
await client
|
||||||
.send('Runtime.releaseObject', { objectId: remoteObject.objectId })
|
.send('Runtime.releaseObject', { objectId: remoteObject.objectId })
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -110,8 +114,9 @@ export function removeEventListeners(
|
|||||||
handler: (...args: any[]) => void;
|
handler: (...args: any[]) => void;
|
||||||
}>
|
}>
|
||||||
): void {
|
): void {
|
||||||
for (const listener of listeners)
|
for (const listener of listeners) {
|
||||||
listener.emitter.removeListener(listener.eventName, listener.handler);
|
listener.emitter.removeListener(listener.eventName, listener.handler);
|
||||||
|
}
|
||||||
listeners.length = 0;
|
listeners.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +143,9 @@ export async function waitForEvent<T>(
|
|||||||
rejectCallback = reject;
|
rejectCallback = reject;
|
||||||
});
|
});
|
||||||
const listener = addEventListener(emitter, eventName, async (event) => {
|
const listener = addEventListener(emitter, eventName, async (event) => {
|
||||||
if (!(await predicate(event))) return;
|
if (!(await predicate(event))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
resolveCallback(event);
|
resolveCallback(event);
|
||||||
});
|
});
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
@ -179,7 +186,9 @@ export function evaluationString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function serializeArgument(arg: unknown): string {
|
function serializeArgument(arg: unknown): string {
|
||||||
if (Object.is(arg, undefined)) return 'undefined';
|
if (Object.is(arg, undefined)) {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
return JSON.stringify(arg);
|
return JSON.stringify(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +275,12 @@ export function makePredicateString(
|
|||||||
waitForVisible: boolean,
|
waitForVisible: boolean,
|
||||||
waitForHidden: boolean
|
waitForHidden: boolean
|
||||||
): Node | null | boolean {
|
): Node | null | boolean {
|
||||||
if (!node) return waitForHidden;
|
if (!node) {
|
||||||
if (!waitForVisible && !waitForHidden) return node;
|
return waitForHidden;
|
||||||
|
}
|
||||||
|
if (!waitForVisible && !waitForHidden) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
const element =
|
const element =
|
||||||
node.nodeType === Node.TEXT_NODE
|
node.nodeType === Node.TEXT_NODE
|
||||||
? (node.parentElement as Element)
|
? (node.parentElement as Element)
|
||||||
@ -307,11 +320,15 @@ export async function waitWithTimeout<T>(
|
|||||||
);
|
);
|
||||||
const timeoutPromise = new Promise<T>((_res, rej) => (reject = rej));
|
const timeoutPromise = new Promise<T>((_res, rej) => (reject = rej));
|
||||||
let timeoutTimer = null;
|
let timeoutTimer = null;
|
||||||
if (timeout) timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
if (timeout) {
|
||||||
|
timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return await Promise.race([promise, timeoutPromise]);
|
return await Promise.race([promise, timeoutPromise]);
|
||||||
} finally {
|
} finally {
|
||||||
if (timeoutTimer) clearTimeout(timeoutTimer);
|
if (timeoutTimer) {
|
||||||
|
clearTimeout(timeoutTimer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,9 @@ export const initializePuppeteer = (packageName: string): PuppeteerNode => {
|
|||||||
process.env['npm_package_config_puppeteer_product']) as Product)
|
process.env['npm_package_config_puppeteer_product']) as Product)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (!isPuppeteerCore && productName === 'firefox')
|
if (!isPuppeteerCore && productName === 'firefox') {
|
||||||
preferredRevision = PUPPETEER_REVISIONS.firefox;
|
preferredRevision = PUPPETEER_REVISIONS.firefox;
|
||||||
|
}
|
||||||
|
|
||||||
return new PuppeteerNode({
|
return new PuppeteerNode({
|
||||||
projectRoot: puppeteerRootDirectory,
|
projectRoot: puppeteerRootDirectory,
|
||||||
|
@ -326,9 +326,12 @@ export class BrowserFetcher {
|
|||||||
assert(fileName, `A malformed download URL was found: ${url}.`);
|
assert(fileName, `A malformed download URL was found: ${url}.`);
|
||||||
const archivePath = path.join(this.#downloadsFolder, fileName);
|
const archivePath = path.join(this.#downloadsFolder, fileName);
|
||||||
const outputPath = this.#getFolderPath(revision);
|
const outputPath = this.#getFolderPath(revision);
|
||||||
if (await existsAsync(outputPath)) return this.revisionInfo(revision);
|
if (await existsAsync(outputPath)) {
|
||||||
if (!(await existsAsync(this.#downloadsFolder)))
|
return this.revisionInfo(revision);
|
||||||
|
}
|
||||||
|
if (!(await existsAsync(this.#downloadsFolder))) {
|
||||||
await mkdirAsync(this.#downloadsFolder);
|
await mkdirAsync(this.#downloadsFolder);
|
||||||
|
}
|
||||||
|
|
||||||
// Use system Chromium builds on Linux ARM devices
|
// Use system Chromium builds on Linux ARM devices
|
||||||
if (os.platform() !== 'darwin' && os.arch() === 'arm64') {
|
if (os.platform() !== 'darwin' && os.arch() === 'arm64') {
|
||||||
@ -339,10 +342,14 @@ export class BrowserFetcher {
|
|||||||
await _downloadFile(url, archivePath, progressCallback);
|
await _downloadFile(url, archivePath, progressCallback);
|
||||||
await install(archivePath, outputPath);
|
await install(archivePath, outputPath);
|
||||||
} finally {
|
} finally {
|
||||||
if (await existsAsync(archivePath)) await unlinkAsync(archivePath);
|
if (await existsAsync(archivePath)) {
|
||||||
|
await unlinkAsync(archivePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const revisionInfo = this.revisionInfo(revision);
|
const revisionInfo = this.revisionInfo(revision);
|
||||||
if (revisionInfo) await chmodAsync(revisionInfo.executablePath, 0o755);
|
if (revisionInfo) {
|
||||||
|
await chmodAsync(revisionInfo.executablePath, 0o755);
|
||||||
|
}
|
||||||
return revisionInfo;
|
return revisionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,7 +360,9 @@ export class BrowserFetcher {
|
|||||||
* available locally on disk.
|
* available locally on disk.
|
||||||
*/
|
*/
|
||||||
async localRevisions(): Promise<string[]> {
|
async localRevisions(): Promise<string[]> {
|
||||||
if (!(await existsAsync(this.#downloadsFolder))) return [];
|
if (!(await existsAsync(this.#downloadsFolder))) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const fileNames = await readdirAsync(this.#downloadsFolder);
|
const fileNames = await readdirAsync(this.#downloadsFolder);
|
||||||
return fileNames
|
return fileNames
|
||||||
.map((fileName) => parseFolderPath(this.#product, fileName))
|
.map((fileName) => parseFolderPath(this.#product, fileName))
|
||||||
@ -390,7 +399,7 @@ export class BrowserFetcher {
|
|||||||
const folderPath = this.#getFolderPath(revision);
|
const folderPath = this.#getFolderPath(revision);
|
||||||
let executablePath = '';
|
let executablePath = '';
|
||||||
if (this.#product === 'chrome') {
|
if (this.#product === 'chrome') {
|
||||||
if (this.#platform === 'mac' || this.#platform === 'mac_arm')
|
if (this.#platform === 'mac' || this.#platform === 'mac_arm') {
|
||||||
executablePath = path.join(
|
executablePath = path.join(
|
||||||
folderPath,
|
folderPath,
|
||||||
archiveName(this.#product, this.#platform, revision),
|
archiveName(this.#product, this.#platform, revision),
|
||||||
@ -399,21 +408,23 @@ export class BrowserFetcher {
|
|||||||
'MacOS',
|
'MacOS',
|
||||||
'Chromium'
|
'Chromium'
|
||||||
);
|
);
|
||||||
else if (this.#platform === 'linux')
|
} else if (this.#platform === 'linux') {
|
||||||
executablePath = path.join(
|
executablePath = path.join(
|
||||||
folderPath,
|
folderPath,
|
||||||
archiveName(this.#product, this.#platform, revision),
|
archiveName(this.#product, this.#platform, revision),
|
||||||
'chrome'
|
'chrome'
|
||||||
);
|
);
|
||||||
else if (this.#platform === 'win32' || this.#platform === 'win64')
|
} else if (this.#platform === 'win32' || this.#platform === 'win64') {
|
||||||
executablePath = path.join(
|
executablePath = path.join(
|
||||||
folderPath,
|
folderPath,
|
||||||
archiveName(this.#product, this.#platform, revision),
|
archiveName(this.#product, this.#platform, revision),
|
||||||
'chrome.exe'
|
'chrome.exe'
|
||||||
);
|
);
|
||||||
else throw new Error('Unsupported platform: ' + this.#platform);
|
} else {
|
||||||
|
throw new Error('Unsupported platform: ' + this.#platform);
|
||||||
|
}
|
||||||
} else if (this.#product === 'firefox') {
|
} else if (this.#product === 'firefox') {
|
||||||
if (this.#platform === 'mac' || this.#platform === 'mac_arm')
|
if (this.#platform === 'mac' || this.#platform === 'mac_arm') {
|
||||||
executablePath = path.join(
|
executablePath = path.join(
|
||||||
folderPath,
|
folderPath,
|
||||||
'Firefox Nightly.app',
|
'Firefox Nightly.app',
|
||||||
@ -421,12 +432,16 @@ export class BrowserFetcher {
|
|||||||
'MacOS',
|
'MacOS',
|
||||||
'firefox'
|
'firefox'
|
||||||
);
|
);
|
||||||
else if (this.#platform === 'linux')
|
} else if (this.#platform === 'linux') {
|
||||||
executablePath = path.join(folderPath, 'firefox', 'firefox');
|
executablePath = path.join(folderPath, 'firefox', 'firefox');
|
||||||
else if (this.#platform === 'win32' || this.#platform === 'win64')
|
} else if (this.#platform === 'win32' || this.#platform === 'win64') {
|
||||||
executablePath = path.join(folderPath, 'firefox', 'firefox.exe');
|
executablePath = path.join(folderPath, 'firefox', 'firefox.exe');
|
||||||
else throw new Error('Unsupported platform: ' + this.#platform);
|
} else {
|
||||||
} else throw new Error('Unsupported product: ' + this.#product);
|
throw new Error('Unsupported platform: ' + this.#platform);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported product: ' + this.#product);
|
||||||
|
}
|
||||||
const url = _downloadURL(
|
const url = _downloadURL(
|
||||||
this.#product,
|
this.#product,
|
||||||
this.#platform,
|
this.#platform,
|
||||||
@ -463,9 +478,13 @@ function parseFolderPath(
|
|||||||
): { product: string; platform: string; revision: string } | undefined {
|
): { product: string; platform: string; revision: string } | undefined {
|
||||||
const name = path.basename(folderPath);
|
const name = path.basename(folderPath);
|
||||||
const splits = name.split('-');
|
const splits = name.split('-');
|
||||||
if (splits.length !== 2) return;
|
if (splits.length !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const [platform, revision] = splits;
|
const [platform, revision] = splits;
|
||||||
if (!revision || !platform || !(platform in downloadURLs[product])) return;
|
if (!revision || !platform || !(platform in downloadURLs[product])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return { product, platform, revision };
|
return { product, platform, revision };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,7 +522,9 @@ function _downloadFile(
|
|||||||
file.on('error', (error) => reject(error));
|
file.on('error', (error) => reject(error));
|
||||||
response.pipe(file);
|
response.pipe(file);
|
||||||
totalBytes = parseInt(response.headers['content-length']!, 10);
|
totalBytes = parseInt(response.headers['content-length']!, 10);
|
||||||
if (progressCallback) response.on('data', onData);
|
if (progressCallback) {
|
||||||
|
response.on('data', onData);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
request.on('error', (error) => reject(error));
|
request.on('error', (error) => reject(error));
|
||||||
return promise;
|
return promise;
|
||||||
@ -516,15 +537,17 @@ function _downloadFile(
|
|||||||
|
|
||||||
function install(archivePath: string, folderPath: string): Promise<unknown> {
|
function install(archivePath: string, folderPath: string): Promise<unknown> {
|
||||||
debugFetcher(`Installing ${archivePath} to ${folderPath}`);
|
debugFetcher(`Installing ${archivePath} to ${folderPath}`);
|
||||||
if (archivePath.endsWith('.zip'))
|
if (archivePath.endsWith('.zip')) {
|
||||||
return extractZip(archivePath, { dir: folderPath });
|
return extractZip(archivePath, { dir: folderPath });
|
||||||
else if (archivePath.endsWith('.tar.bz2'))
|
} else if (archivePath.endsWith('.tar.bz2')) {
|
||||||
return _extractTar(archivePath, folderPath);
|
return _extractTar(archivePath, folderPath);
|
||||||
else if (archivePath.endsWith('.dmg'))
|
} else if (archivePath.endsWith('.dmg')) {
|
||||||
return mkdirAsync(folderPath).then(() =>
|
return mkdirAsync(folderPath).then(() =>
|
||||||
_installDMG(archivePath, folderPath)
|
_installDMG(archivePath, folderPath)
|
||||||
);
|
);
|
||||||
else throw new Error(`Unsupported archive format: ${archivePath}`);
|
} else {
|
||||||
|
throw new Error(`Unsupported archive format: ${archivePath}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -549,23 +572,30 @@ function _installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
|||||||
return new Promise<void>((fulfill, reject): void => {
|
return new Promise<void>((fulfill, reject): void => {
|
||||||
const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`;
|
const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`;
|
||||||
childProcess.exec(mountCommand, (err, stdout) => {
|
childProcess.exec(mountCommand, (err, stdout) => {
|
||||||
if (err) return reject(err);
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
const volumes = stdout.match(/\/Volumes\/(.*)/m);
|
const volumes = stdout.match(/\/Volumes\/(.*)/m);
|
||||||
if (!volumes)
|
if (!volumes) {
|
||||||
return reject(new Error(`Could not find volume path in ${stdout}`));
|
return reject(new Error(`Could not find volume path in ${stdout}`));
|
||||||
|
}
|
||||||
mountPath = volumes[0]!;
|
mountPath = volumes[0]!;
|
||||||
readdirAsync(mountPath)
|
readdirAsync(mountPath)
|
||||||
.then((fileNames) => {
|
.then((fileNames) => {
|
||||||
const appName = fileNames.find(
|
const appName = fileNames.find(
|
||||||
(item) => typeof item === 'string' && item.endsWith('.app')
|
(item) => typeof item === 'string' && item.endsWith('.app')
|
||||||
);
|
);
|
||||||
if (!appName)
|
if (!appName) {
|
||||||
return reject(new Error(`Cannot find app in ${mountPath}`));
|
return reject(new Error(`Cannot find app in ${mountPath}`));
|
||||||
|
}
|
||||||
const copyPath = path.join(mountPath!, appName);
|
const copyPath = path.join(mountPath!, appName);
|
||||||
debugFetcher(`Copying ${copyPath} to ${folderPath}`);
|
debugFetcher(`Copying ${copyPath} to ${folderPath}`);
|
||||||
childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => {
|
childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => {
|
||||||
if (err) reject(err);
|
if (err) {
|
||||||
else fulfill();
|
reject(err);
|
||||||
|
} else {
|
||||||
|
fulfill();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@ -575,11 +605,15 @@ function _installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
})
|
})
|
||||||
.finally((): void => {
|
.finally((): void => {
|
||||||
if (!mountPath) return;
|
if (!mountPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const unmountCommand = `hdiutil detach "${mountPath}" -quiet`;
|
const unmountCommand = `hdiutil detach "${mountPath}" -quiet`;
|
||||||
debugFetcher(`Unmounting ${mountPath}`);
|
debugFetcher(`Unmounting ${mountPath}`);
|
||||||
childProcess.exec(unmountCommand, (err) => {
|
childProcess.exec(unmountCommand, (err) => {
|
||||||
if (err) console.error(`Error unmounting dmg: ${err}`);
|
if (err) {
|
||||||
|
console.error(`Error unmounting dmg: ${err}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -637,9 +671,11 @@ function httpRequest(
|
|||||||
res.statusCode >= 300 &&
|
res.statusCode >= 300 &&
|
||||||
res.statusCode < 400 &&
|
res.statusCode < 400 &&
|
||||||
res.headers.location
|
res.headers.location
|
||||||
)
|
) {
|
||||||
httpRequest(res.headers.location, method, response);
|
httpRequest(res.headers.location, method, response);
|
||||||
else response(res);
|
} else {
|
||||||
|
response(res);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const request =
|
const request =
|
||||||
options.protocol === 'https:'
|
options.protocol === 'https:'
|
||||||
|
@ -80,11 +80,17 @@ export class BrowserRunner {
|
|||||||
options;
|
options;
|
||||||
let stdio: Array<'ignore' | 'pipe'>;
|
let stdio: Array<'ignore' | 'pipe'>;
|
||||||
if (pipe) {
|
if (pipe) {
|
||||||
if (dumpio) stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
|
if (dumpio) {
|
||||||
else stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
|
stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
|
||||||
|
} else {
|
||||||
|
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dumpio) stdio = ['pipe', 'pipe', 'pipe'];
|
if (dumpio) {
|
||||||
else stdio = ['pipe', 'ignore', 'pipe'];
|
stdio = ['pipe', 'pipe', 'pipe'];
|
||||||
|
} else {
|
||||||
|
stdio = ['pipe', 'ignore', 'pipe'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(!this.proc, 'This process has previously been started.');
|
assert(!this.proc, 'This process has previously been started.');
|
||||||
debugLauncher(
|
debugLauncher(
|
||||||
@ -147,25 +153,30 @@ export class BrowserRunner {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.#listeners = [addEventListener(process, 'exit', this.kill.bind(this))];
|
this.#listeners = [addEventListener(process, 'exit', this.kill.bind(this))];
|
||||||
if (handleSIGINT)
|
if (handleSIGINT) {
|
||||||
this.#listeners.push(
|
this.#listeners.push(
|
||||||
addEventListener(process, 'SIGINT', () => {
|
addEventListener(process, 'SIGINT', () => {
|
||||||
this.kill();
|
this.kill();
|
||||||
process.exit(130);
|
process.exit(130);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (handleSIGTERM)
|
}
|
||||||
|
if (handleSIGTERM) {
|
||||||
this.#listeners.push(
|
this.#listeners.push(
|
||||||
addEventListener(process, 'SIGTERM', this.close.bind(this))
|
addEventListener(process, 'SIGTERM', this.close.bind(this))
|
||||||
);
|
);
|
||||||
if (handleSIGHUP)
|
}
|
||||||
|
if (handleSIGHUP) {
|
||||||
this.#listeners.push(
|
this.#listeners.push(
|
||||||
addEventListener(process, 'SIGHUP', this.close.bind(this))
|
addEventListener(process, 'SIGHUP', this.close.bind(this))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): Promise<void> {
|
close(): Promise<void> {
|
||||||
if (this.#closed) return Promise.resolve();
|
if (this.#closed) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
if (this.#isTempUserDataDir) {
|
if (this.#isTempUserDataDir) {
|
||||||
this.kill();
|
this.kill();
|
||||||
} else if (this.connection) {
|
} else if (this.connection) {
|
||||||
@ -309,14 +320,18 @@ function waitForWSEndpoint(
|
|||||||
function onLine(line: string): void {
|
function onLine(line: string): void {
|
||||||
stderr += line + '\n';
|
stderr += line + '\n';
|
||||||
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
||||||
if (!match) return;
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
// The RegExp matches, so this will obviously exist.
|
// The RegExp matches, so this will obviously exist.
|
||||||
resolve(match[1]!);
|
resolve(match[1]!);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup(): void {
|
function cleanup(): void {
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
removeEventListeners(listeners);
|
removeEventListeners(listeners);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -96,14 +96,17 @@ class ChromeLauncher implements ProductLauncher {
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const chromeArguments = [];
|
const chromeArguments = [];
|
||||||
if (!ignoreDefaultArgs) chromeArguments.push(...this.defaultArgs(options));
|
if (!ignoreDefaultArgs) {
|
||||||
else if (Array.isArray(ignoreDefaultArgs))
|
chromeArguments.push(...this.defaultArgs(options));
|
||||||
|
} else if (Array.isArray(ignoreDefaultArgs)) {
|
||||||
chromeArguments.push(
|
chromeArguments.push(
|
||||||
...this.defaultArgs(options).filter(
|
...this.defaultArgs(options).filter(
|
||||||
(arg) => !ignoreDefaultArgs.includes(arg)
|
(arg) => !ignoreDefaultArgs.includes(arg)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
else chromeArguments.push(...args);
|
} else {
|
||||||
|
chromeArguments.push(...args);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!chromeArguments.some((argument) =>
|
!chromeArguments.some((argument) =>
|
||||||
@ -248,9 +251,12 @@ class ChromeLauncher implements ProductLauncher {
|
|||||||
args = [],
|
args = [],
|
||||||
userDataDir,
|
userDataDir,
|
||||||
} = options;
|
} = options;
|
||||||
if (userDataDir)
|
if (userDataDir) {
|
||||||
chromeArguments.push(`--user-data-dir=${path.resolve(userDataDir)}`);
|
chromeArguments.push(`--user-data-dir=${path.resolve(userDataDir)}`);
|
||||||
if (devtools) chromeArguments.push('--auto-open-devtools-for-tabs');
|
}
|
||||||
|
if (devtools) {
|
||||||
|
chromeArguments.push('--auto-open-devtools-for-tabs');
|
||||||
|
}
|
||||||
if (headless) {
|
if (headless) {
|
||||||
chromeArguments.push(
|
chromeArguments.push(
|
||||||
headless === 'chrome' ? '--headless=chrome' : '--headless',
|
headless === 'chrome' ? '--headless=chrome' : '--headless',
|
||||||
@ -258,8 +264,9 @@ class ChromeLauncher implements ProductLauncher {
|
|||||||
'--mute-audio'
|
'--mute-audio'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (args.every((arg) => arg.startsWith('-')))
|
if (args.every((arg) => arg.startsWith('-'))) {
|
||||||
chromeArguments.push('about:blank');
|
chromeArguments.push('about:blank');
|
||||||
|
}
|
||||||
chromeArguments.push(...args);
|
chromeArguments.push(...args);
|
||||||
return chromeArguments;
|
return chromeArguments;
|
||||||
}
|
}
|
||||||
@ -326,14 +333,17 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const firefoxArguments = [];
|
const firefoxArguments = [];
|
||||||
if (!ignoreDefaultArgs) firefoxArguments.push(...this.defaultArgs(options));
|
if (!ignoreDefaultArgs) {
|
||||||
else if (Array.isArray(ignoreDefaultArgs))
|
firefoxArguments.push(...this.defaultArgs(options));
|
||||||
|
} else if (Array.isArray(ignoreDefaultArgs)) {
|
||||||
firefoxArguments.push(
|
firefoxArguments.push(
|
||||||
...this.defaultArgs(options).filter(
|
...this.defaultArgs(options).filter(
|
||||||
(arg) => !ignoreDefaultArgs.includes(arg)
|
(arg) => !ignoreDefaultArgs.includes(arg)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
else firefoxArguments.push(...args);
|
} else {
|
||||||
|
firefoxArguments.push(...args);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!firefoxArguments.some((argument) =>
|
!firefoxArguments.some((argument) =>
|
||||||
@ -379,7 +389,9 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
let firefoxExecutable = executablePath;
|
let firefoxExecutable = executablePath;
|
||||||
if (!executablePath) {
|
if (!executablePath) {
|
||||||
const { missingText, executablePath } = resolveExecutablePath(this);
|
const { missingText, executablePath } = resolveExecutablePath(this);
|
||||||
if (missingText) throw new Error(missingText);
|
if (missingText) {
|
||||||
|
throw new Error(missingText);
|
||||||
|
}
|
||||||
firefoxExecutable = executablePath;
|
firefoxExecutable = executablePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +464,9 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
product: this.product,
|
product: this.product,
|
||||||
});
|
});
|
||||||
const localRevisions = await browserFetcher.localRevisions();
|
const localRevisions = await browserFetcher.localRevisions();
|
||||||
if (localRevisions[0]) this._preferredRevision = localRevisions[0];
|
if (localRevisions[0]) {
|
||||||
|
this._preferredRevision = localRevisions[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,18 +484,24 @@ class FirefoxLauncher implements ProductLauncher {
|
|||||||
|
|
||||||
const firefoxArguments = ['--no-remote'];
|
const firefoxArguments = ['--no-remote'];
|
||||||
|
|
||||||
if (os.platform() === 'darwin') firefoxArguments.push('--foreground');
|
if (os.platform() === 'darwin') {
|
||||||
else if (os.platform().startsWith('win')) {
|
firefoxArguments.push('--foreground');
|
||||||
|
} else if (os.platform().startsWith('win')) {
|
||||||
firefoxArguments.push('--wait-for-browser');
|
firefoxArguments.push('--wait-for-browser');
|
||||||
}
|
}
|
||||||
if (userDataDir) {
|
if (userDataDir) {
|
||||||
firefoxArguments.push('--profile');
|
firefoxArguments.push('--profile');
|
||||||
firefoxArguments.push(userDataDir);
|
firefoxArguments.push(userDataDir);
|
||||||
}
|
}
|
||||||
if (headless) firefoxArguments.push('--headless');
|
if (headless) {
|
||||||
if (devtools) firefoxArguments.push('--devtools');
|
firefoxArguments.push('--headless');
|
||||||
if (args.every((arg) => arg.startsWith('-')))
|
}
|
||||||
|
if (devtools) {
|
||||||
|
firefoxArguments.push('--devtools');
|
||||||
|
}
|
||||||
|
if (args.every((arg) => arg.startsWith('-'))) {
|
||||||
firefoxArguments.push('about:blank');
|
firefoxArguments.push('about:blank');
|
||||||
|
}
|
||||||
firefoxArguments.push(...args);
|
firefoxArguments.push(...args);
|
||||||
return firefoxArguments;
|
return firefoxArguments;
|
||||||
}
|
}
|
||||||
@ -887,11 +907,12 @@ export default function Launcher(
|
|||||||
product?: string
|
product?: string
|
||||||
): ProductLauncher {
|
): ProductLauncher {
|
||||||
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
||||||
if (!product && !isPuppeteerCore)
|
if (!product && !isPuppeteerCore) {
|
||||||
product =
|
product =
|
||||||
process.env['PUPPETEER_PRODUCT'] ||
|
process.env['PUPPETEER_PRODUCT'] ||
|
||||||
process.env['npm_config_puppeteer_product'] ||
|
process.env['npm_config_puppeteer_product'] ||
|
||||||
process.env['npm_package_config_puppeteer_product'];
|
process.env['npm_package_config_puppeteer_product'];
|
||||||
|
}
|
||||||
switch (product) {
|
switch (product) {
|
||||||
case 'firefox':
|
case 'firefox':
|
||||||
return new FirefoxLauncher(
|
return new FirefoxLauncher(
|
||||||
|
@ -57,10 +57,14 @@ export class NodeWebSocketTransport implements ConnectionTransport {
|
|||||||
constructor(ws: NodeWebSocket) {
|
constructor(ws: NodeWebSocket) {
|
||||||
this.#ws = ws;
|
this.#ws = ws;
|
||||||
this.#ws.addEventListener('message', (event) => {
|
this.#ws.addEventListener('message', (event) => {
|
||||||
if (this.onmessage) this.onmessage.call(null, event.data);
|
if (this.onmessage) {
|
||||||
|
this.onmessage.call(null, event.data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.#ws.addEventListener('close', () => {
|
this.#ws.addEventListener('close', () => {
|
||||||
if (this.onclose) this.onclose.call(null);
|
if (this.onclose) {
|
||||||
|
this.onclose.call(null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Silently ignore all errors - we don't know what to do with them.
|
// Silently ignore all errors - we don't know what to do with them.
|
||||||
this.#ws.addEventListener('error', () => {});
|
this.#ws.addEventListener('error', () => {});
|
||||||
|
@ -116,7 +116,9 @@ export class PuppeteerNode extends Puppeteer {
|
|||||||
* @returns Promise which resolves to browser instance.
|
* @returns Promise which resolves to browser instance.
|
||||||
*/
|
*/
|
||||||
override connect(options: ConnectOptions): Promise<Browser> {
|
override connect(options: ConnectOptions): Promise<Browser> {
|
||||||
if (options.product) this._productName = options.product;
|
if (options.product) {
|
||||||
|
this._productName = options.product;
|
||||||
|
}
|
||||||
return super.connect(options);
|
return super.connect(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +133,9 @@ export class PuppeteerNode extends Puppeteer {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
set _productName(name: Product | undefined) {
|
set _productName(name: Product | undefined) {
|
||||||
if (this.#productName !== name) this._changedProduct = true;
|
if (this.#productName !== name) {
|
||||||
|
this._changedProduct = true;
|
||||||
|
}
|
||||||
this.#productName = name;
|
this.#productName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +165,9 @@ export class PuppeteerNode extends Puppeteer {
|
|||||||
* @returns Promise which resolves to browser instance.
|
* @returns Promise which resolves to browser instance.
|
||||||
*/
|
*/
|
||||||
launch(options: PuppeteerLaunchOptions = {}): Promise<Browser> {
|
launch(options: PuppeteerLaunchOptions = {}): Promise<Browser> {
|
||||||
if (options.product) this._productName = options.product;
|
if (options.product) {
|
||||||
|
this._productName = options.product;
|
||||||
|
}
|
||||||
return this._launcher.launch(options);
|
return this._launcher.launch(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +97,15 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
process.env['npm_config_http_proxy'] || process.env['npm_config_proxy'];
|
process.env['npm_config_http_proxy'] || process.env['npm_config_proxy'];
|
||||||
const NPM_NO_PROXY = process.env['npm_config_no_proxy'];
|
const NPM_NO_PROXY = process.env['npm_config_no_proxy'];
|
||||||
|
|
||||||
if (NPM_HTTPS_PROXY) process.env['HTTPS_PROXY'] = NPM_HTTPS_PROXY;
|
if (NPM_HTTPS_PROXY) {
|
||||||
if (NPM_HTTP_PROXY) process.env['HTTP_PROXY'] = NPM_HTTP_PROXY;
|
process.env['HTTPS_PROXY'] = NPM_HTTPS_PROXY;
|
||||||
if (NPM_NO_PROXY) process.env['NO_PROXY'] = NPM_NO_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 {
|
function onSuccess(localRevisions: string[]): void {
|
||||||
logPolitely(
|
logPolitely(
|
||||||
@ -182,8 +188,9 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
);
|
);
|
||||||
https
|
https
|
||||||
.get(firefoxVersionsUrl, requestOptions, (r) => {
|
.get(firefoxVersionsUrl, requestOptions, (r) => {
|
||||||
if (r.statusCode && r.statusCode >= 400)
|
if (r.statusCode && r.statusCode >= 400) {
|
||||||
return reject(new Error(`Got status code ${r.statusCode}`));
|
return reject(new Error(`Got status code ${r.statusCode}`));
|
||||||
|
}
|
||||||
r.on('data', (chunk) => {
|
r.on('data', (chunk) => {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
});
|
});
|
||||||
@ -207,5 +214,7 @@ export function logPolitely(toBeLogged: unknown): void {
|
|||||||
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
|
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
if (!logLevelDisplay) console.log(toBeLogged);
|
if (!logLevelDisplay) {
|
||||||
|
console.log(toBeLogged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -493,10 +493,14 @@ describeFailsFirefox('Accessibility', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
function findFocusedNode(node) {
|
function findFocusedNode(node) {
|
||||||
if (node.focused) return node;
|
if (node.focused) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
for (const child of node.children || []) {
|
for (const child of node.children || []) {
|
||||||
const focusedChild = findFocusedNode(child);
|
const focusedChild = findFocusedNode(child);
|
||||||
if (focusedChild) return focusedChild;
|
if (focusedChild) {
|
||||||
|
return focusedChild;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,6 @@
|
|||||||
);
|
);
|
||||||
function updateButtons(buttons) {
|
function updateButtons(buttons) {
|
||||||
for (let i = 0; i < 5; i++)
|
for (let i = 0; i < 5; i++)
|
||||||
box.classList.toggle('button-' + i, buttons & (1 << i));
|
{box.classList.toggle('button-' + i, buttons & (1 << i));}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -38,8 +38,11 @@ describe('Browser specs', function () {
|
|||||||
|
|
||||||
const userAgent = await browser.userAgent();
|
const userAgent = await browser.userAgent();
|
||||||
expect(userAgent.length).toBeGreaterThan(0);
|
expect(userAgent.length).toBeGreaterThan(0);
|
||||||
if (isChrome) expect(userAgent).toContain('WebKit');
|
if (isChrome) {
|
||||||
else expect(userAgent).toContain('Gecko');
|
expect(userAgent).toContain('WebKit');
|
||||||
|
} else {
|
||||||
|
expect(userAgent).toContain('Gecko');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,7 +113,9 @@ describe('BrowserContext', function () {
|
|||||||
resolved = true;
|
resolved = true;
|
||||||
if (error instanceof puppeteer.errors.TimeoutError) {
|
if (error instanceof puppeteer.errors.TimeoutError) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else throw error;
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
expect(resolved).toBe(false);
|
expect(resolved).toBe(false);
|
||||||
@ -124,7 +126,9 @@ describe('BrowserContext', function () {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof puppeteer.errors.TimeoutError) {
|
if (error instanceof puppeteer.errors.TimeoutError) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else throw error;
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await context.close();
|
await context.close();
|
||||||
});
|
});
|
||||||
|
@ -85,8 +85,9 @@ function traceAPICoverage(apiCoverage, className, modulePath) {
|
|||||||
typeof methodName !== 'string' ||
|
typeof methodName !== 'string' ||
|
||||||
methodName.startsWith('_') ||
|
methodName.startsWith('_') ||
|
||||||
typeof method !== 'function'
|
typeof method !== 'function'
|
||||||
)
|
) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
apiCoverage.set(`${className}.${methodName}`, false);
|
apiCoverage.set(`${className}.${methodName}`, false);
|
||||||
Reflect.set(classType.prototype, methodName, function (...args) {
|
Reflect.set(classType.prototype, methodName, function (...args) {
|
||||||
apiCoverage.set(`${className}.${methodName}`, true);
|
apiCoverage.set(`${className}.${methodName}`, true);
|
||||||
@ -102,13 +103,15 @@ function traceAPICoverage(apiCoverage, className, modulePath) {
|
|||||||
const eventsName = `${className}EmittedEvents`;
|
const eventsName = `${className}EmittedEvents`;
|
||||||
if (loadedModule[eventsName]) {
|
if (loadedModule[eventsName]) {
|
||||||
for (const event of Object.values(loadedModule[eventsName])) {
|
for (const event of Object.values(loadedModule[eventsName])) {
|
||||||
if (typeof event !== 'symbol')
|
if (typeof event !== 'symbol') {
|
||||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
|
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const method = Reflect.get(classType.prototype, 'emit');
|
const method = Reflect.get(classType.prototype, 'emit');
|
||||||
Reflect.set(classType.prototype, 'emit', function (event, ...args) {
|
Reflect.set(classType.prototype, 'emit', function (event, ...args) {
|
||||||
if (typeof event !== 'symbol' && this.listenerCount(event))
|
if (typeof event !== 'symbol' && this.listenerCount(event)) {
|
||||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
|
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
|
||||||
|
}
|
||||||
return method.call(this, event, ...args);
|
return method.call(this, event, ...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -49,9 +49,11 @@ describe('ElementHandle specs', function () {
|
|||||||
const nestedFrame = page.frames()[1].childFrames()[1];
|
const nestedFrame = page.frames()[1].childFrames()[1];
|
||||||
const elementHandle = await nestedFrame.$('div');
|
const elementHandle = await nestedFrame.$('div');
|
||||||
const box = await elementHandle.boundingBox();
|
const box = await elementHandle.boundingBox();
|
||||||
if (isChrome)
|
if (isChrome) {
|
||||||
expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 });
|
expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 });
|
||||||
else expect(box).toEqual({ x: 28, y: 182, width: 254, height: 18 });
|
} else {
|
||||||
|
expect(box).toEqual({ x: 28, y: 182, width: 254, height: 18 });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should return null for invisible elements', async () => {
|
it('should return null for invisible elements', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
|
@ -75,8 +75,9 @@ describe('Fixtures', function () {
|
|||||||
let output = '';
|
let output = '';
|
||||||
res.stdout.on('data', (data) => {
|
res.stdout.on('data', (data) => {
|
||||||
output += data;
|
output += data;
|
||||||
if (output.indexOf('\n'))
|
if (output.indexOf('\n')) {
|
||||||
wsEndPointCallback(output.substring(0, output.indexOf('\n')));
|
wsEndPointCallback(output.substring(0, output.indexOf('\n')));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const browser = await puppeteer.connect({
|
const browser = await puppeteer.connect({
|
||||||
browserWSEndpoint: await wsEndPointPromise,
|
browserWSEndpoint: await wsEndPointPromise,
|
||||||
@ -85,9 +86,11 @@ describe('Fixtures', function () {
|
|||||||
new Promise((resolve) => browser.once('disconnected', resolve)),
|
new Promise((resolve) => browser.once('disconnected', resolve)),
|
||||||
new Promise((resolve) => res.on('close', resolve)),
|
new Promise((resolve) => res.on('close', resolve)),
|
||||||
];
|
];
|
||||||
if (process.platform === 'win32')
|
if (process.platform === 'win32') {
|
||||||
execSync(`taskkill /pid ${res.pid} /T /F`);
|
execSync(`taskkill /pid ${res.pid} /T /F`);
|
||||||
else process.kill(res.pid);
|
} else {
|
||||||
|
process.kill(res.pid);
|
||||||
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -37,8 +37,9 @@ const GoldenComparators = {
|
|||||||
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
|
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
|
||||||
*/
|
*/
|
||||||
function compareImages(actualBuffer, expectedBuffer, mimeType) {
|
function compareImages(actualBuffer, expectedBuffer, mimeType) {
|
||||||
if (!actualBuffer || !(actualBuffer instanceof Buffer))
|
if (!actualBuffer || !(actualBuffer instanceof Buffer)) {
|
||||||
return { errorMessage: 'Actual result should be Buffer.' };
|
return { errorMessage: 'Actual result should be Buffer.' };
|
||||||
|
}
|
||||||
|
|
||||||
const actual =
|
const actual =
|
||||||
mimeType === 'image/png'
|
mimeType === 'image/png'
|
||||||
@ -71,10 +72,13 @@ function compareImages(actualBuffer, expectedBuffer, mimeType) {
|
|||||||
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
|
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
|
||||||
*/
|
*/
|
||||||
function compareText(actual, expectedBuffer) {
|
function compareText(actual, expectedBuffer) {
|
||||||
if (typeof actual !== 'string')
|
if (typeof actual !== 'string') {
|
||||||
return { errorMessage: 'Actual result should be string' };
|
return { errorMessage: 'Actual result should be string' };
|
||||||
|
}
|
||||||
const expected = expectedBuffer.toString('utf-8');
|
const expected = expectedBuffer.toString('utf-8');
|
||||||
if (expected === actual) return null;
|
if (expected === actual) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const diff = new Diff();
|
const diff = new Diff();
|
||||||
const result = diff.main(expected, actual);
|
const result = diff.main(expected, actual);
|
||||||
diff.cleanupSemantic(result);
|
diff.cleanupSemantic(result);
|
||||||
@ -120,7 +124,9 @@ function compare(goldenPath, outputPath, actual, goldenName) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const result = comparator(actual, expected, mimeType);
|
const result = comparator(actual, expected, mimeType);
|
||||||
if (!result) return { pass: true };
|
if (!result) {
|
||||||
|
return { pass: true };
|
||||||
|
}
|
||||||
ensureOutputDir();
|
ensureOutputDir();
|
||||||
if (goldenPath === outputPath) {
|
if (goldenPath === outputPath) {
|
||||||
fs.writeFileSync(addSuffix(actualPath, '-actual'), actual);
|
fs.writeFileSync(addSuffix(actualPath, '-actual'), actual);
|
||||||
@ -135,14 +141,18 @@ function compare(goldenPath, outputPath, actual, goldenName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message = goldenName + ' mismatch!';
|
let message = goldenName + ' mismatch!';
|
||||||
if (result.errorMessage) message += ' ' + result.errorMessage;
|
if (result.errorMessage) {
|
||||||
|
message += ' ' + result.errorMessage;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
pass: false,
|
pass: false,
|
||||||
message: message + ' ' + messageSuffix,
|
message: message + ' ' + messageSuffix,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ensureOutputDir() {
|
function ensureOutputDir() {
|
||||||
if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath);
|
if (!fs.existsSync(outputPath)) {
|
||||||
|
fs.mkdirSync(outputPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,15 +338,17 @@ describeChromeOnly('headful tests', function () {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < N; ++i)
|
for (let i = 0; i < N; ++i) {
|
||||||
promises.push(
|
promises.push(
|
||||||
pages[i].screenshot({
|
pages[i].screenshot({
|
||||||
clip: { x: 50 * i, y: 0, width: 50, height: 50 },
|
clip: { x: 50 * i, y: 0, width: 50, height: 50 },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const screenshots = await Promise.all(promises);
|
const screenshots = await Promise.all(promises);
|
||||||
for (let i = 0; i < N; ++i)
|
for (let i = 0; i < N; ++i) {
|
||||||
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
|
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
|
||||||
|
}
|
||||||
await Promise.all(pages.map((page) => page.close()));
|
await Promise.all(pages.map((page) => page.close()));
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
@ -152,9 +152,11 @@ describe('JSHandle', function () {
|
|||||||
const windowHandle = await page.evaluateHandle('window');
|
const windowHandle = await page.evaluateHandle('window');
|
||||||
let error = null;
|
let error = null;
|
||||||
await windowHandle.jsonValue().catch((error_) => (error = error_));
|
await windowHandle.jsonValue().catch((error_) => (error = error_));
|
||||||
if (isChrome)
|
if (isChrome) {
|
||||||
expect(error.message).toContain('Object reference chain is too long');
|
expect(error.message).toContain('Object reference chain is too long');
|
||||||
else expect(error.message).toContain('Object is not serializable');
|
} else {
|
||||||
|
expect(error.message).toContain('Object is not serializable');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,14 +64,17 @@ describe('Keyboard', function () {
|
|||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => document.querySelector('textarea').value)
|
await page.evaluate(() => document.querySelector('textarea').value)
|
||||||
).toBe('Hello World!');
|
).toBe('Hello World!');
|
||||||
for (let i = 0; i < 'World!'.length; i++) page.keyboard.press('ArrowLeft');
|
for (let i = 0; i < 'World!'.length; i++) {
|
||||||
|
page.keyboard.press('ArrowLeft');
|
||||||
|
}
|
||||||
await page.keyboard.type('inserted ');
|
await page.keyboard.type('inserted ');
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => document.querySelector('textarea').value)
|
await page.evaluate(() => document.querySelector('textarea').value)
|
||||||
).toBe('Hello inserted World!');
|
).toBe('Hello inserted World!');
|
||||||
page.keyboard.down('Shift');
|
page.keyboard.down('Shift');
|
||||||
for (let i = 0; i < 'inserted '.length; i++)
|
for (let i = 0; i < 'inserted '.length; i++) {
|
||||||
page.keyboard.press('ArrowLeft');
|
page.keyboard.press('ArrowLeft');
|
||||||
|
}
|
||||||
page.keyboard.up('Shift');
|
page.keyboard.up('Shift');
|
||||||
await page.keyboard.press('Backspace');
|
await page.keyboard.press('Backspace');
|
||||||
expect(
|
expect(
|
||||||
@ -152,7 +155,7 @@ describe('Keyboard', function () {
|
|||||||
);
|
);
|
||||||
await keyboard.down('!');
|
await keyboard.down('!');
|
||||||
// Shift+! will generate a keypress
|
// Shift+! will generate a keypress
|
||||||
if (modifierKey === 'Shift')
|
if (modifierKey === 'Shift') {
|
||||||
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
||||||
'Keydown: ! Digit1 49 [' +
|
'Keydown: ! Digit1 49 [' +
|
||||||
modifierKey +
|
modifierKey +
|
||||||
@ -160,10 +163,11 @@ describe('Keyboard', function () {
|
|||||||
modifierKey +
|
modifierKey +
|
||||||
']'
|
']'
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
||||||
'Keydown: ! Digit1 49 [' + modifierKey + ']'
|
'Keydown: ! Digit1 49 [' + modifierKey + ']'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await keyboard.up('!');
|
await keyboard.up('!');
|
||||||
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
expect(await page.evaluate(() => globalThis.getResult())).toBe(
|
||||||
@ -260,8 +264,12 @@ describe('Keyboard', function () {
|
|||||||
(event) => {
|
(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
if (event.key === 'l') event.preventDefault();
|
if (event.key === 'l') {
|
||||||
if (event.key === 'o') event.preventDefault();
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (event.key === 'o') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@ -396,13 +404,22 @@ describe('Keyboard', function () {
|
|||||||
string,
|
string,
|
||||||
boolean
|
boolean
|
||||||
];
|
];
|
||||||
if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS');
|
if (isFirefox && os.platform() !== 'darwin') {
|
||||||
else expect(key).toBe('Meta');
|
expect(key).toBe('OS');
|
||||||
|
} else {
|
||||||
|
expect(key).toBe('Meta');
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirefox) expect(code).toBe('OSLeft');
|
if (isFirefox) {
|
||||||
else expect(code).toBe('MetaLeft');
|
expect(code).toBe('OSLeft');
|
||||||
|
} else {
|
||||||
|
expect(code).toBe('MetaLeft');
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirefox && os.platform() !== 'darwin') expect(metaKey).toBe(false);
|
if (isFirefox && os.platform() !== 'darwin') {
|
||||||
else expect(metaKey).toBe(true);
|
expect(metaKey).toBe(false);
|
||||||
|
} else {
|
||||||
|
expect(metaKey).toBe(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,9 @@ const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
|||||||
const FIREFOX_TIMEOUT = 30 * 1000;
|
const FIREFOX_TIMEOUT = 30 * 1000;
|
||||||
|
|
||||||
describe('Launcher specs', function () {
|
describe('Launcher specs', function () {
|
||||||
if (getTestState().isFirefox) this.timeout(FIREFOX_TIMEOUT);
|
if (getTestState().isFirefox) {
|
||||||
|
this.timeout(FIREFOX_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
describe('Puppeteer', function () {
|
describe('Puppeteer', function () {
|
||||||
describe('BrowserFetcher', function () {
|
describe('BrowserFetcher', function () {
|
||||||
@ -394,8 +396,11 @@ describe('Launcher specs', function () {
|
|||||||
});
|
});
|
||||||
it('should report the correct product', async () => {
|
it('should report the correct product', async () => {
|
||||||
const { isChrome, isFirefox, puppeteer } = getTestState();
|
const { isChrome, isFirefox, puppeteer } = getTestState();
|
||||||
if (isChrome) expect(puppeteer.product).toBe('chrome');
|
if (isChrome) {
|
||||||
else if (isFirefox) expect(puppeteer.product).toBe('firefox');
|
expect(puppeteer.product).toBe('chrome');
|
||||||
|
} else if (isFirefox) {
|
||||||
|
expect(puppeteer.product).toBe('firefox');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with no default arguments', async () => {
|
it('should work with no default arguments', async () => {
|
||||||
const { defaultBrowserOptions, puppeteer } = getTestState();
|
const { defaultBrowserOptions, puppeteer } = getTestState();
|
||||||
@ -468,7 +473,9 @@ describe('Launcher specs', function () {
|
|||||||
const pages = await browser.pages();
|
const pages = await browser.pages();
|
||||||
expect(pages.length).toBe(1);
|
expect(pages.length).toBe(1);
|
||||||
const page = pages[0];
|
const page = pages[0];
|
||||||
if (page.url() !== server.EMPTY_PAGE) await page.waitForNavigation();
|
if (page.url() !== server.EMPTY_PAGE) {
|
||||||
|
await page.waitForNavigation();
|
||||||
|
}
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE);
|
expect(page.url()).toBe(server.EMPTY_PAGE);
|
||||||
await browser.close();
|
await browser.close();
|
||||||
}
|
}
|
||||||
|
@ -99,15 +99,18 @@ const defaultBrowserOptions = Object.assign(
|
|||||||
} else {
|
} else {
|
||||||
// TODO(jackfranklin): declare updateRevision in some form for the Firefox
|
// TODO(jackfranklin): declare updateRevision in some form for the Firefox
|
||||||
// launcher.
|
// launcher.
|
||||||
// @ts-expect-error _updateRevision is defined on the FF launcher
|
if (product === 'firefox') {
|
||||||
// but not the Chrome one. The types need tidying so that TS can infer that
|
// @ts-expect-error _updateRevision is defined on the FF launcher
|
||||||
// properly and not error here.
|
// but not the Chrome one. The types need tidying so that TS can infer that
|
||||||
if (product === 'firefox') await puppeteer._launcher._updateRevision();
|
// properly and not error here.
|
||||||
|
await puppeteer._launcher._updateRevision();
|
||||||
|
}
|
||||||
const executablePath = puppeteer.executablePath();
|
const executablePath = puppeteer.executablePath();
|
||||||
if (!fs.existsSync(executablePath))
|
if (!fs.existsSync(executablePath)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Browser is not downloaded at ${executablePath}. Run 'npm install' and try to re-run tests`
|
`Browser is not downloaded at ${executablePath}. Run 'npm install' and try to re-run tests`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -121,7 +124,9 @@ const setupGoldenAssertions = (): void => {
|
|||||||
const suffix = product.toLowerCase();
|
const suffix = product.toLowerCase();
|
||||||
const GOLDEN_DIR = path.join(__dirname, 'golden-' + suffix);
|
const GOLDEN_DIR = path.join(__dirname, 'golden-' + suffix);
|
||||||
const OUTPUT_DIR = path.join(__dirname, 'output-' + suffix);
|
const OUTPUT_DIR = path.join(__dirname, 'output-' + suffix);
|
||||||
if (fs.existsSync(OUTPUT_DIR)) rimraf.sync(OUTPUT_DIR);
|
if (fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
rimraf.sync(OUTPUT_DIR);
|
||||||
|
}
|
||||||
utils.extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR);
|
utils.extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,41 +154,55 @@ export const itFailsFirefox = (
|
|||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (isFirefox) return xit(description, body);
|
if (isFirefox) {
|
||||||
else return it(description, body);
|
return xit(description, body);
|
||||||
|
} else {
|
||||||
|
return it(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const itChromeOnly = (
|
export const itChromeOnly = (
|
||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (isChrome) return it(description, body);
|
if (isChrome) {
|
||||||
else return xit(description, body);
|
return it(description, body);
|
||||||
|
} else {
|
||||||
|
return xit(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const itHeadlessOnly = (
|
export const itHeadlessOnly = (
|
||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (isChrome && isHeadless === true) return it(description, body);
|
if (isChrome && isHeadless === true) {
|
||||||
else return xit(description, body);
|
return it(description, body);
|
||||||
|
} else {
|
||||||
|
return xit(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const itFirefoxOnly = (
|
export const itFirefoxOnly = (
|
||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (isFirefox) return it(description, body);
|
if (isFirefox) {
|
||||||
else return xit(description, body);
|
return it(description, body);
|
||||||
|
} else {
|
||||||
|
return xit(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const itOnlyRegularInstall = (
|
export const itOnlyRegularInstall = (
|
||||||
description: string,
|
description: string,
|
||||||
body: Mocha.Func
|
body: Mocha.Func
|
||||||
): Mocha.Test => {
|
): Mocha.Test => {
|
||||||
if (alternativeInstall || process.env['BINARY'])
|
if (alternativeInstall || process.env['BINARY']) {
|
||||||
return xit(description, body);
|
return xit(description, body);
|
||||||
else return it(description, body);
|
} else {
|
||||||
|
return it(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const itFailsWindowsUntilDate = (
|
export const itFailsWindowsUntilDate = (
|
||||||
@ -213,15 +232,20 @@ export const describeFailsFirefox = (
|
|||||||
description: string,
|
description: string,
|
||||||
body: (this: Mocha.Suite) => void
|
body: (this: Mocha.Suite) => void
|
||||||
): void | Mocha.Suite => {
|
): void | Mocha.Suite => {
|
||||||
if (isFirefox) return xdescribe(description, body);
|
if (isFirefox) {
|
||||||
else return describe(description, body);
|
return xdescribe(description, body);
|
||||||
|
} else {
|
||||||
|
return describe(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const describeChromeOnly = (
|
export const describeChromeOnly = (
|
||||||
description: string,
|
description: string,
|
||||||
body: (this: Mocha.Suite) => void
|
body: (this: Mocha.Suite) => void
|
||||||
): Mocha.Suite | void => {
|
): Mocha.Suite | void => {
|
||||||
if (isChrome) return describe(description, body);
|
if (isChrome) {
|
||||||
|
return describe(description, body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -161,20 +161,26 @@ describe('Mouse', function () {
|
|||||||
['Meta', 'metaKey'],
|
['Meta', 'metaKey'],
|
||||||
]);
|
]);
|
||||||
// In Firefox, the Meta modifier only exists on Mac
|
// In Firefox, the Meta modifier only exists on Mac
|
||||||
if (isFirefox && os.platform() !== 'darwin') delete modifiers['Meta'];
|
if (isFirefox && os.platform() !== 'darwin') {
|
||||||
|
delete modifiers['Meta'];
|
||||||
|
}
|
||||||
for (const [modifier, key] of modifiers) {
|
for (const [modifier, key] of modifiers) {
|
||||||
await page.keyboard.down(modifier);
|
await page.keyboard.down(modifier);
|
||||||
await page.click('#button-3');
|
await page.click('#button-3');
|
||||||
if (
|
if (
|
||||||
!(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
|
!(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
|
||||||
)
|
) {
|
||||||
throw new Error(key + ' should be true');
|
throw new Error(key + ' should be true');
|
||||||
|
}
|
||||||
await page.keyboard.up(modifier);
|
await page.keyboard.up(modifier);
|
||||||
}
|
}
|
||||||
await page.click('#button-3');
|
await page.click('#button-3');
|
||||||
for (const [modifier, key] of modifiers) {
|
for (const [modifier, key] of modifiers) {
|
||||||
if (await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
|
if (
|
||||||
|
await page.evaluate((mod: string) => globalThis.lastEvent[mod], key)
|
||||||
|
) {
|
||||||
throw new Error(modifiers[modifier] + ' should be false');
|
throw new Error(modifiers[modifier] + ' should be false');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
itFailsFirefox('should send mouse wheel events', async () => {
|
itFailsFirefox('should send mouse wheel events', async () => {
|
||||||
|
@ -88,8 +88,11 @@ describe('navigation', function () {
|
|||||||
let error = null;
|
let error = null;
|
||||||
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
||||||
expect(error).not.toBe(null);
|
expect(error).not.toBe(null);
|
||||||
if (isChrome) expect(error.message).toContain('net::ERR_ABORTED');
|
if (isChrome) {
|
||||||
else expect(error.message).toContain('NS_BINDING_ABORTED');
|
expect(error.message).toContain('net::ERR_ABORTED');
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('NS_BINDING_ABORTED');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should navigate to empty page with domcontentloaded', async () => {
|
it('should navigate to empty page with domcontentloaded', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -140,9 +143,11 @@ describe('navigation', function () {
|
|||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
await page.goto('asdfasdf').catch((error_) => (error = error_));
|
await page.goto('asdfasdf').catch((error_) => (error = error_));
|
||||||
if (isChrome)
|
if (isChrome) {
|
||||||
expect(error.message).toContain('Cannot navigate to invalid URL');
|
expect(error.message).toContain('Cannot navigate to invalid URL');
|
||||||
else expect(error.message).toContain('Invalid url');
|
} else {
|
||||||
|
expect(error.message).toContain('Invalid url');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* If you are running this on pre-Catalina versions of macOS this will fail locally.
|
/* If you are running this on pre-Catalina versions of macOS this will fail locally.
|
||||||
@ -169,8 +174,11 @@ describe('navigation', function () {
|
|||||||
await page
|
await page
|
||||||
.goto(httpsServer.EMPTY_PAGE)
|
.goto(httpsServer.EMPTY_PAGE)
|
||||||
.catch((error_) => (error = error_));
|
.catch((error_) => (error = error_));
|
||||||
if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
|
if (isChrome) {
|
||||||
else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
||||||
|
}
|
||||||
|
|
||||||
expect(requests.length).toBe(2);
|
expect(requests.length).toBe(2);
|
||||||
expect(requests[0]).toBe('request');
|
expect(requests[0]).toBe('request');
|
||||||
@ -185,8 +193,11 @@ describe('navigation', function () {
|
|||||||
await page
|
await page
|
||||||
.goto(httpsServer.PREFIX + '/redirect/1.html')
|
.goto(httpsServer.PREFIX + '/redirect/1.html')
|
||||||
.catch((error_) => (error = error_));
|
.catch((error_) => (error = error_));
|
||||||
if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
|
if (isChrome) {
|
||||||
else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should throw if networkidle is passed as an option', async () => {
|
it('should throw if networkidle is passed as an option', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -207,9 +218,11 @@ describe('navigation', function () {
|
|||||||
await page
|
await page
|
||||||
.goto('http://localhost:44123/non-existing-url')
|
.goto('http://localhost:44123/non-existing-url')
|
||||||
.catch((error_) => (error = error_));
|
.catch((error_) => (error = error_));
|
||||||
if (isChrome)
|
if (isChrome) {
|
||||||
expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
|
expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
|
||||||
else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED');
|
} else {
|
||||||
|
expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should fail when exceeding maximum navigation timeout', async () => {
|
it('should fail when exceeding maximum navigation timeout', async () => {
|
||||||
const { page, server, puppeteer } = getTestState();
|
const { page, server, puppeteer } = getTestState();
|
||||||
@ -385,7 +398,9 @@ describe('navigation', function () {
|
|||||||
let warning = null;
|
let warning = null;
|
||||||
const warningHandler = (w) => (warning = w);
|
const warningHandler = (w) => (warning = w);
|
||||||
process.on('warning', warningHandler);
|
process.on('warning', warningHandler);
|
||||||
for (let i = 0; i < 20; ++i) await page.goto(server.EMPTY_PAGE);
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
}
|
||||||
process.removeListener('warning', warningHandler);
|
process.removeListener('warning', warningHandler);
|
||||||
expect(warning).toBe(null);
|
expect(warning).toBe(null);
|
||||||
});
|
});
|
||||||
@ -395,10 +410,11 @@ describe('navigation', function () {
|
|||||||
let warning = null;
|
let warning = null;
|
||||||
const warningHandler = (w) => (warning = w);
|
const warningHandler = (w) => (warning = w);
|
||||||
process.on('warning', warningHandler);
|
process.on('warning', warningHandler);
|
||||||
for (let i = 0; i < 20; ++i)
|
for (let i = 0; i < 20; ++i) {
|
||||||
await page.goto('asdf').catch(() => {
|
await page.goto('asdf').catch(() => {
|
||||||
/* swallow navigation error */
|
/* swallow navigation error */
|
||||||
});
|
});
|
||||||
|
}
|
||||||
process.removeListener('warning', warningHandler);
|
process.removeListener('warning', warningHandler);
|
||||||
expect(warning).toBe(null);
|
expect(warning).toBe(null);
|
||||||
});
|
});
|
||||||
@ -615,7 +631,9 @@ describe('navigation', function () {
|
|||||||
const frame = await utils.waitEvent(page, 'frameattached');
|
const frame = await utils.waitEvent(page, 'frameattached');
|
||||||
await new Promise<void>((fulfill) => {
|
await new Promise<void>((fulfill) => {
|
||||||
page.on('framenavigated', (f) => {
|
page.on('framenavigated', (f) => {
|
||||||
if (f === frame) fulfill();
|
if (f === frame) {
|
||||||
|
fulfill();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -255,7 +255,9 @@ describe('network', function () {
|
|||||||
server.setRoute('/post', (req, res) => res.end());
|
server.setRoute('/post', (req, res) => res.end());
|
||||||
let request = null;
|
let request = null;
|
||||||
page.on('request', (r) => {
|
page.on('request', (r) => {
|
||||||
if (!utils.isFavicon(r)) request = r;
|
if (!utils.isFavicon(r)) {
|
||||||
|
request = r;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await page.evaluate(() =>
|
await page.evaluate(() =>
|
||||||
fetch('./post', {
|
fetch('./post', {
|
||||||
@ -504,8 +506,11 @@ describe('network', function () {
|
|||||||
|
|
||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('css')) request.abort();
|
if (request.url().endsWith('css')) {
|
||||||
else request.continue();
|
request.abort();
|
||||||
|
} else {
|
||||||
|
request.continue();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const failedRequests = [];
|
const failedRequests = [];
|
||||||
page.on('requestfailed', (request) => failedRequests.push(request));
|
page.on('requestfailed', (request) => failedRequests.push(request));
|
||||||
@ -514,10 +519,11 @@ describe('network', function () {
|
|||||||
expect(failedRequests[0].url()).toContain('one-style.css');
|
expect(failedRequests[0].url()).toContain('one-style.css');
|
||||||
expect(failedRequests[0].response()).toBe(null);
|
expect(failedRequests[0].response()).toBe(null);
|
||||||
expect(failedRequests[0].resourceType()).toBe('stylesheet');
|
expect(failedRequests[0].resourceType()).toBe('stylesheet');
|
||||||
if (isChrome)
|
if (isChrome) {
|
||||||
expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED');
|
expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED');
|
||||||
else
|
} else {
|
||||||
expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
|
expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
|
||||||
|
}
|
||||||
expect(failedRequests[0].frame()).toBeTruthy();
|
expect(failedRequests[0].frame()).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('Page.Events.RequestFinished', async () => {
|
it('Page.Events.RequestFinished', async () => {
|
||||||
|
@ -67,8 +67,11 @@ describe('Page', function () {
|
|||||||
const dialog = await waitEvent(newPage, 'dialog');
|
const dialog = await waitEvent(newPage, 'dialog');
|
||||||
expect(dialog.type()).toBe('beforeunload');
|
expect(dialog.type()).toBe('beforeunload');
|
||||||
expect(dialog.defaultValue()).toBe('');
|
expect(dialog.defaultValue()).toBe('');
|
||||||
if (isChrome) expect(dialog.message()).toBe('');
|
if (isChrome) {
|
||||||
else expect(dialog.message()).toBeTruthy();
|
expect(dialog.message()).toBe('');
|
||||||
|
} else {
|
||||||
|
expect(dialog.message()).toBeTruthy();
|
||||||
|
}
|
||||||
await dialog.accept();
|
await dialog.accept();
|
||||||
await pageClosingPromise;
|
await pageClosingPromise;
|
||||||
});
|
});
|
||||||
@ -313,7 +316,9 @@ describe('Page', function () {
|
|||||||
const { page, server, context, isHeadless } = getTestState();
|
const { page, server, context, isHeadless } = getTestState();
|
||||||
|
|
||||||
// TODO: re-enable this test in headful once crbug.com/1324480 rolls out.
|
// TODO: re-enable this test in headful once crbug.com/1324480 rolls out.
|
||||||
if (!isHeadless) return;
|
if (!isHeadless) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
@ -585,8 +590,11 @@ describe('Page', function () {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
expect(message.text()).toContain('Access-Control-Allow-Origin');
|
expect(message.text()).toContain('Access-Control-Allow-Origin');
|
||||||
if (isChrome) expect(message.type()).toEqual('error');
|
if (isChrome) {
|
||||||
else expect(message.type()).toEqual('warn');
|
expect(message.type()).toEqual('error');
|
||||||
|
} else {
|
||||||
|
expect(message.type()).toEqual('warn');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should have location when fetch fails', async () => {
|
it('should have location when fetch fails', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -1260,7 +1268,9 @@ describe('Page', function () {
|
|||||||
it('should work fast enough', async () => {
|
it('should work fast enough', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
|
|
||||||
for (let i = 0; i < 20; ++i) await page.setContent('<div>yo</div>');
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
await page.setContent('<div>yo</div>');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with tricky content', async () => {
|
it('should work with tricky content', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
@ -1703,7 +1713,9 @@ describe('Page', function () {
|
|||||||
// Printing to pdf is currently only supported in headless
|
// Printing to pdf is currently only supported in headless
|
||||||
const { isHeadless, page } = getTestState();
|
const { isHeadless, page } = getTestState();
|
||||||
|
|
||||||
if (!isHeadless) return;
|
if (!isHeadless) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const outputFile = __dirname + '/assets/output.pdf';
|
const outputFile = __dirname + '/assets/output.pdf';
|
||||||
await page.pdf({ path: outputFile });
|
await page.pdf({ path: outputFile });
|
||||||
@ -1715,7 +1727,9 @@ describe('Page', function () {
|
|||||||
// Printing to pdf is currently only supported in headless
|
// Printing to pdf is currently only supported in headless
|
||||||
const { isHeadless, page } = getTestState();
|
const { isHeadless, page } = getTestState();
|
||||||
|
|
||||||
if (!isHeadless) return;
|
if (!isHeadless) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const stream = await page.createPDFStream();
|
const stream = await page.createPDFStream();
|
||||||
let size = 0;
|
let size = 0;
|
||||||
@ -1727,7 +1741,9 @@ describe('Page', function () {
|
|||||||
|
|
||||||
it('should respect timeout', async () => {
|
it('should respect timeout', async () => {
|
||||||
const { isHeadless, page, server, puppeteer } = getTestState();
|
const { isHeadless, page, server, puppeteer } = getTestState();
|
||||||
if (!isHeadless) return;
|
if (!isHeadless) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/pdf.html');
|
await page.goto(server.PREFIX + '/pdf.html');
|
||||||
|
|
||||||
|
@ -40,33 +40,42 @@ describe('request interception', function () {
|
|||||||
const actionResults: ActionResult[] = [];
|
const actionResults: ActionResult[] = [];
|
||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('.css'))
|
if (request.url().endsWith('.css')) {
|
||||||
request.continue(
|
request.continue(
|
||||||
{ headers: { ...request.headers(), xaction: 'continue' } },
|
{ headers: { ...request.headers(), xaction: 'continue' } },
|
||||||
expectedAction === 'continue' ? 1 : 0
|
expectedAction === 'continue' ? 1 : 0
|
||||||
);
|
);
|
||||||
else request.continue({}, 0);
|
} else {
|
||||||
|
request.continue({}, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('.css'))
|
if (request.url().endsWith('.css')) {
|
||||||
request.respond(
|
request.respond(
|
||||||
{ headers: { xaction: 'respond' } },
|
{ headers: { xaction: 'respond' } },
|
||||||
expectedAction === 'respond' ? 1 : 0
|
expectedAction === 'respond' ? 1 : 0
|
||||||
);
|
);
|
||||||
else request.continue({}, 0);
|
} else {
|
||||||
|
request.continue({}, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('.css'))
|
if (request.url().endsWith('.css')) {
|
||||||
request.abort('aborted', expectedAction === 'abort' ? 1 : 0);
|
request.abort('aborted', expectedAction === 'abort' ? 1 : 0);
|
||||||
else request.continue({}, 0);
|
} else {
|
||||||
|
request.continue({}, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
page.on('response', (response) => {
|
page.on('response', (response) => {
|
||||||
const { xaction } = response.headers();
|
const { xaction } = response.headers();
|
||||||
if (response.url().endsWith('.css') && !!xaction)
|
if (response.url().endsWith('.css') && !!xaction) {
|
||||||
actionResults.push(xaction as ActionResult);
|
actionResults.push(xaction as ActionResult);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
page.on('requestfailed', (request) => {
|
page.on('requestfailed', (request) => {
|
||||||
if (request.url().endsWith('.css')) actionResults.push('abort');
|
if (request.url().endsWith('.css')) {
|
||||||
|
actionResults.push('abort');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await (async () => {
|
const response = await (async () => {
|
||||||
@ -172,7 +181,9 @@ describe('request interception', function () {
|
|||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
const requests = [];
|
const requests = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (!utils.isFavicon(request)) requests.push(request);
|
if (!utils.isFavicon(request)) {
|
||||||
|
requests.push(request);
|
||||||
|
}
|
||||||
request.continue({}, 0);
|
request.continue({}, 0);
|
||||||
});
|
});
|
||||||
await page.goto(server.PREFIX + '/one-style.html');
|
await page.goto(server.PREFIX + '/one-style.html');
|
||||||
@ -248,8 +259,11 @@ describe('request interception', function () {
|
|||||||
|
|
||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('.css')) request.abort('failed', 0);
|
if (request.url().endsWith('.css')) {
|
||||||
else request.continue({}, 0);
|
request.abort('failed', 0);
|
||||||
|
} else {
|
||||||
|
request.continue({}, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let failedRequests = 0;
|
let failedRequests = 0;
|
||||||
page.on('requestfailed', () => ++failedRequests);
|
page.on('requestfailed', () => ++failedRequests);
|
||||||
@ -310,8 +324,11 @@ describe('request interception', function () {
|
|||||||
let error = null;
|
let error = null;
|
||||||
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
if (isChrome) expect(error.message).toContain('net::ERR_FAILED');
|
if (isChrome) {
|
||||||
else expect(error.message).toContain('NS_ERROR_FAILURE');
|
expect(error.message).toContain('net::ERR_FAILED');
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('NS_ERROR_FAILURE');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with redirects', async () => {
|
it('should work with redirects', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -360,7 +377,9 @@ describe('request interception', function () {
|
|||||||
const requests = [];
|
const requests = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
request.continue({}, 0);
|
request.continue({}, 0);
|
||||||
if (!utils.isFavicon(request)) requests.push(request);
|
if (!utils.isFavicon(request)) {
|
||||||
|
requests.push(request);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
server.setRedirect('/one-style.css', '/two-style.css');
|
server.setRedirect('/one-style.css', '/two-style.css');
|
||||||
server.setRedirect('/two-style.css', '/three-style.css');
|
server.setRedirect('/two-style.css', '/three-style.css');
|
||||||
@ -388,9 +407,11 @@ describe('request interception', function () {
|
|||||||
server.setRedirect('/non-existing.json', '/non-existing-2.json');
|
server.setRedirect('/non-existing.json', '/non-existing-2.json');
|
||||||
server.setRedirect('/non-existing-2.json', '/simple.html');
|
server.setRedirect('/non-existing-2.json', '/simple.html');
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().includes('non-existing-2'))
|
if (request.url().includes('non-existing-2')) {
|
||||||
request.abort('failed', 0);
|
request.abort('failed', 0);
|
||||||
else request.continue({}, 0);
|
} else {
|
||||||
|
request.continue({}, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const result = await page.evaluate(async () => {
|
const result = await page.evaluate(async () => {
|
||||||
@ -400,8 +421,11 @@ describe('request interception', function () {
|
|||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isChrome) expect(result).toContain('Failed to fetch');
|
if (isChrome) {
|
||||||
else expect(result).toContain('NetworkError');
|
expect(result).toContain('Failed to fetch');
|
||||||
|
} else {
|
||||||
|
expect(result).toContain('NetworkError');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with equal requests', async () => {
|
it('should work with equal requests', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -868,6 +892,8 @@ describe('request interception', function () {
|
|||||||
function pathToFileURL(path: string): string {
|
function pathToFileURL(path: string): string {
|
||||||
let pathName = path.replace(/\\/g, '/');
|
let pathName = path.replace(/\\/g, '/');
|
||||||
// Windows drive letter must be prefixed with a slash.
|
// Windows drive letter must be prefixed with a slash.
|
||||||
if (!pathName.startsWith('/')) pathName = '/' + pathName;
|
if (!pathName.startsWith('/')) {
|
||||||
|
pathName = '/' + pathName;
|
||||||
|
}
|
||||||
return 'file://' + pathName;
|
return 'file://' + pathName;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,9 @@ describe('request interception', function () {
|
|||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
const requests = [];
|
const requests = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (!utils.isFavicon(request)) requests.push(request);
|
if (!utils.isFavicon(request)) {
|
||||||
|
requests.push(request);
|
||||||
|
}
|
||||||
request.continue();
|
request.continue();
|
||||||
});
|
});
|
||||||
await page.goto(server.PREFIX + '/one-style.html');
|
await page.goto(server.PREFIX + '/one-style.html');
|
||||||
@ -187,8 +189,11 @@ describe('request interception', function () {
|
|||||||
|
|
||||||
await page.setRequestInterception(true);
|
await page.setRequestInterception(true);
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().endsWith('.css')) request.abort();
|
if (request.url().endsWith('.css')) {
|
||||||
else request.continue();
|
request.abort();
|
||||||
|
} else {
|
||||||
|
request.continue();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let failedRequests = 0;
|
let failedRequests = 0;
|
||||||
page.on('requestfailed', () => ++failedRequests);
|
page.on('requestfailed', () => ++failedRequests);
|
||||||
@ -234,8 +239,11 @@ describe('request interception', function () {
|
|||||||
let error = null;
|
let error = null;
|
||||||
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
if (isChrome) expect(error.message).toContain('net::ERR_FAILED');
|
if (isChrome) {
|
||||||
else expect(error.message).toContain('NS_ERROR_FAILURE');
|
expect(error.message).toContain('net::ERR_FAILED');
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('NS_ERROR_FAILURE');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with redirects', async () => {
|
it('should work with redirects', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -284,7 +292,9 @@ describe('request interception', function () {
|
|||||||
const requests = [];
|
const requests = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
request.continue();
|
request.continue();
|
||||||
if (!utils.isFavicon(request)) requests.push(request);
|
if (!utils.isFavicon(request)) {
|
||||||
|
requests.push(request);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
server.setRedirect('/one-style.css', '/two-style.css');
|
server.setRedirect('/one-style.css', '/two-style.css');
|
||||||
server.setRedirect('/two-style.css', '/three-style.css');
|
server.setRedirect('/two-style.css', '/three-style.css');
|
||||||
@ -312,8 +322,11 @@ describe('request interception', function () {
|
|||||||
server.setRedirect('/non-existing.json', '/non-existing-2.json');
|
server.setRedirect('/non-existing.json', '/non-existing-2.json');
|
||||||
server.setRedirect('/non-existing-2.json', '/simple.html');
|
server.setRedirect('/non-existing-2.json', '/simple.html');
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
if (request.url().includes('non-existing-2')) request.abort();
|
if (request.url().includes('non-existing-2')) {
|
||||||
else request.continue();
|
request.abort();
|
||||||
|
} else {
|
||||||
|
request.continue();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const result = await page.evaluate(async () => {
|
const result = await page.evaluate(async () => {
|
||||||
@ -323,8 +336,11 @@ describe('request interception', function () {
|
|||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isChrome) expect(result).toContain('Failed to fetch');
|
if (isChrome) {
|
||||||
else expect(result).toContain('NetworkError');
|
expect(result).toContain('Failed to fetch');
|
||||||
|
} else {
|
||||||
|
expect(result).toContain('NetworkError');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should work with equal requests', async () => {
|
it('should work with equal requests', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -809,6 +825,8 @@ describe('request interception', function () {
|
|||||||
function pathToFileURL(path: string): string {
|
function pathToFileURL(path: string): string {
|
||||||
let pathName = path.replace(/\\/g, '/');
|
let pathName = path.replace(/\\/g, '/');
|
||||||
// Windows drive letter must be prefixed with a slash.
|
// Windows drive letter must be prefixed with a slash.
|
||||||
if (!pathName.startsWith('/')) pathName = '/' + pathName;
|
if (!pathName.startsWith('/')) {
|
||||||
|
pathName = '/' + pathName;
|
||||||
|
}
|
||||||
return 'file://' + pathName;
|
return 'file://' + pathName;
|
||||||
}
|
}
|
||||||
|
@ -112,15 +112,17 @@ describe('Screenshots', function () {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < N; ++i)
|
for (let i = 0; i < N; ++i) {
|
||||||
promises.push(
|
promises.push(
|
||||||
pages[i].screenshot({
|
pages[i].screenshot({
|
||||||
clip: { x: 50 * i, y: 0, width: 50, height: 50 },
|
clip: { x: 50 * i, y: 0, width: 50, height: 50 },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const screenshots = await Promise.all(promises);
|
const screenshots = await Promise.all(promises);
|
||||||
for (let i = 0; i < N; ++i)
|
for (let i = 0; i < N; ++i) {
|
||||||
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
|
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
|
||||||
|
}
|
||||||
await Promise.all(pages.map((page) => page.close()));
|
await Promise.all(pages.map((page) => page.close()));
|
||||||
});
|
});
|
||||||
itFailsFirefox('should allow transparency', async () => {
|
itFailsFirefox('should allow transparency', async () => {
|
||||||
|
@ -288,7 +288,9 @@ describe('Target', function () {
|
|||||||
resolved = true;
|
resolved = true;
|
||||||
if (error instanceof puppeteer.errors.TimeoutError) {
|
if (error instanceof puppeteer.errors.TimeoutError) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else throw error;
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
expect(resolved).toBe(false);
|
expect(resolved).toBe(false);
|
||||||
@ -299,7 +301,9 @@ describe('Target', function () {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof puppeteer.errors.TimeoutError) {
|
if (error instanceof puppeteer.errors.TimeoutError) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else throw error;
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
@ -111,10 +111,13 @@ const utils = (module.exports = {
|
|||||||
dumpFrames: function (frame, indentation) {
|
dumpFrames: function (frame, indentation) {
|
||||||
indentation = indentation || '';
|
indentation = indentation || '';
|
||||||
let description = frame.url().replace(/:\d{4}\//, ':<PORT>/');
|
let description = frame.url().replace(/:\d{4}\//, ':<PORT>/');
|
||||||
if (frame.name()) description += ' (' + frame.name() + ')';
|
if (frame.name()) {
|
||||||
|
description += ' (' + frame.name() + ')';
|
||||||
|
}
|
||||||
const result = [indentation + description];
|
const result = [indentation + description];
|
||||||
for (const child of frame.childFrames())
|
for (const child of frame.childFrames()) {
|
||||||
result.push(...utils.dumpFrames(child, ' ' + indentation));
|
result.push(...utils.dumpFrames(child, ' ' + indentation));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -126,7 +129,9 @@ const utils = (module.exports = {
|
|||||||
waitEvent: function (emitter, eventName, predicate = () => true) {
|
waitEvent: function (emitter, eventName, predicate = () => true) {
|
||||||
return new Promise((fulfill) => {
|
return new Promise((fulfill) => {
|
||||||
emitter.on(eventName, function listener(event) {
|
emitter.on(eventName, function listener(event) {
|
||||||
if (!predicate(event)) return;
|
if (!predicate(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
emitter.removeListener(eventName, listener);
|
emitter.removeListener(eventName, listener);
|
||||||
fulfill(event);
|
fulfill(event);
|
||||||
});
|
});
|
||||||
|
@ -146,7 +146,9 @@ describe('waittask specs', function () {
|
|||||||
|
|
||||||
await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true));
|
await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true));
|
||||||
await page.waitForFunction(() => {
|
await page.waitForFunction(() => {
|
||||||
if (!globalThis.__RELOADED) window.location.reload();
|
if (!globalThis.__RELOADED) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -49,13 +49,17 @@ async function compileTypeScript() {
|
|||||||
async function compileTypeScriptIfRequired() {
|
async function compileTypeScriptIfRequired() {
|
||||||
const libPath = path.join(__dirname, 'lib');
|
const libPath = path.join(__dirname, 'lib');
|
||||||
const libExists = await fileExists(libPath);
|
const libExists = await fileExists(libPath);
|
||||||
if (libExists) return;
|
if (libExists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Puppeteer:', 'Compiling TypeScript...');
|
console.log('Puppeteer:', 'Compiling TypeScript...');
|
||||||
await compileTypeScript();
|
await compileTypeScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's being run as node typescript-if-required.js, not require('..')
|
// It's being run as node typescript-if-required.js, not require('..')
|
||||||
if (require.main === module) compileTypeScriptIfRequired();
|
if (require.main === module) {
|
||||||
|
compileTypeScriptIfRequired();
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = compileTypeScriptIfRequired;
|
module.exports = compileTypeScriptIfRequired;
|
||||||
|
@ -27,7 +27,9 @@ class ESTreeWalker {
|
|||||||
* @param {?ESTree.Node} parent
|
* @param {?ESTree.Node} parent
|
||||||
*/
|
*/
|
||||||
_innerWalk(node, parent) {
|
_innerWalk(node, parent) {
|
||||||
if (!node) return;
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
node.parent = parent;
|
node.parent = parent;
|
||||||
|
|
||||||
if (this._beforeVisit.call(null, node) === ESTreeWalker.SkipSubtree) {
|
if (this._beforeVisit.call(null, node) === ESTreeWalker.SkipSubtree) {
|
||||||
@ -36,7 +38,9 @@ class ESTreeWalker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const walkOrder = ESTreeWalker._walkOrder[node.type];
|
const walkOrder = ESTreeWalker._walkOrder[node.type];
|
||||||
if (!walkOrder) return;
|
if (!walkOrder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (node.type === 'TemplateLiteral') {
|
if (node.type === 'TemplateLiteral') {
|
||||||
const templateLiteral = /** @type {!ESTree.TemplateLiteralNode} */ (node);
|
const templateLiteral = /** @type {!ESTree.TemplateLiteralNode} */ (node);
|
||||||
@ -52,8 +56,11 @@ class ESTreeWalker {
|
|||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < walkOrder.length; ++i) {
|
for (let i = 0; i < walkOrder.length; ++i) {
|
||||||
const entity = node[walkOrder[i]];
|
const entity = node[walkOrder[i]];
|
||||||
if (Array.isArray(entity)) this._walkArray(entity, node);
|
if (Array.isArray(entity)) {
|
||||||
else this._innerWalk(entity, node);
|
this._walkArray(entity, node);
|
||||||
|
} else {
|
||||||
|
this._innerWalk(entity, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,8 +72,9 @@ class ESTreeWalker {
|
|||||||
* @param {?ESTree.Node} parentNode
|
* @param {?ESTree.Node} parentNode
|
||||||
*/
|
*/
|
||||||
_walkArray(nodeArray, parentNode) {
|
_walkArray(nodeArray, parentNode) {
|
||||||
for (let i = 0; i < nodeArray.length; ++i)
|
for (let i = 0; i < nodeArray.length; ++i) {
|
||||||
this._innerWalk(nodeArray[i], parentNode);
|
this._innerWalk(nodeArray[i], parentNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,14 +111,18 @@ if (argv.script && !fs.existsSync(scriptPath)) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const middle = Math.round((good + bad) / 2);
|
const middle = Math.round((good + bad) / 2);
|
||||||
const revision = await findDownloadableRevision(middle, good, bad);
|
const revision = await findDownloadableRevision(middle, good, bad);
|
||||||
if (!revision || revision === good || revision === bad) break;
|
if (!revision || revision === good || revision === bad) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
let info = browserFetcher.revisionInfo(revision);
|
let info = browserFetcher.revisionInfo(revision);
|
||||||
const shouldRemove = noCache && !info.local;
|
const shouldRemove = noCache && !info.local;
|
||||||
info = await downloadRevision(revision);
|
info = await downloadRevision(revision);
|
||||||
const exitCode = await (pattern
|
const exitCode = await (pattern
|
||||||
? runUnitTest(pattern, info)
|
? runUnitTest(pattern, info)
|
||||||
: runScript(scriptPath, info));
|
: runScript(scriptPath, info));
|
||||||
if (shouldRemove) await browserFetcher.remove(revision);
|
if (shouldRemove) {
|
||||||
|
await browserFetcher.remove(revision);
|
||||||
|
}
|
||||||
let outcome;
|
let outcome;
|
||||||
if (exitCode) {
|
if (exitCode) {
|
||||||
bad = revision;
|
bad = revision;
|
||||||
@ -224,7 +228,9 @@ async function findDownloadableRevision(rev, from, to) {
|
|||||||
const min = Math.min(from, to);
|
const min = Math.min(from, to);
|
||||||
const max = Math.max(from, to);
|
const max = Math.max(from, to);
|
||||||
log(`Looking around ${rev} from [${min}, ${max}]`);
|
log(`Looking around ${rev} from [${min}, ${max}]`);
|
||||||
if (await browserFetcher.canDownload(rev)) return rev;
|
if (await browserFetcher.canDownload(rev)) {
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
let down = rev;
|
let down = rev;
|
||||||
let up = rev;
|
let up = rev;
|
||||||
while (min <= down || up <= max) {
|
while (min <= down || up <= max) {
|
||||||
@ -232,8 +238,12 @@ async function findDownloadableRevision(rev, from, to) {
|
|||||||
down > min ? probe(--down) : Promise.resolve(false),
|
down > min ? probe(--down) : Promise.resolve(false),
|
||||||
up < max ? probe(++up) : Promise.resolve(false),
|
up < max ? probe(++up) : Promise.resolve(false),
|
||||||
]);
|
]);
|
||||||
if (downOk) return down;
|
if (downOk) {
|
||||||
if (upOk) return up;
|
return down;
|
||||||
|
}
|
||||||
|
if (upOk) {
|
||||||
|
return up;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -283,6 +293,8 @@ function getChromiumRevision(gitRevision = null) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const m = result.match(/chromium: '(\d+)'/);
|
const m = result.match(/chromium: '(\d+)'/);
|
||||||
if (!m) return null;
|
if (!m) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return parseInt(m[1], 10);
|
return parseInt(m[1], 10);
|
||||||
}
|
}
|
||||||
|
@ -43,8 +43,9 @@ class Table {
|
|||||||
drawRow(values) {
|
drawRow(values) {
|
||||||
assert(values.length === this.widths.length);
|
assert(values.length === this.widths.length);
|
||||||
let row = '';
|
let row = '';
|
||||||
for (let i = 0; i < values.length; ++i)
|
for (let i = 0; i < values.length; ++i) {
|
||||||
row += padCenter(values[i], this.widths[i]);
|
row += padCenter(values[i], this.widths[i]);
|
||||||
|
}
|
||||||
console.log(row);
|
console.log(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,9 +263,13 @@ async function checkRangeAvailability({
|
|||||||
}
|
}
|
||||||
table.drawRow(values);
|
table.drawRow(values);
|
||||||
} else {
|
} else {
|
||||||
if (allAvailable) console.log(revision);
|
if (allAvailable) {
|
||||||
|
console.log(revision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allAvailable && stopWhenAllAvailable) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (allAvailable && stopWhenAllAvailable) break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +333,9 @@ function filterOutColors(text) {
|
|||||||
*/
|
*/
|
||||||
function padCenter(text, length) {
|
function padCenter(text, length) {
|
||||||
const printableCharacters = filterOutColors(text);
|
const printableCharacters = filterOutColors(text);
|
||||||
if (printableCharacters.length >= length) return text;
|
if (printableCharacters.length >= length) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
const left = Math.floor((length - printableCharacters.length) / 2);
|
const left = Math.floor((length - printableCharacters.length) / 2);
|
||||||
const right = Math.ceil((length - printableCharacters.length) / 2);
|
const right = Math.ceil((length - printableCharacters.length) / 2);
|
||||||
return spaceString(left) + text + spaceString(right);
|
return spaceString(left) + text + spaceString(right);
|
||||||
|
@ -63,7 +63,9 @@ class Source {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
setText(text) {
|
setText(text) {
|
||||||
if (text === this._text) return false;
|
if (text === this._text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this._hasUpdatedText = true;
|
this._hasUpdatedText = true;
|
||||||
this._text = text;
|
this._text = text;
|
||||||
return true;
|
return true;
|
||||||
|
@ -22,7 +22,9 @@ class Documentation {
|
|||||||
this.classesArray = classesArray;
|
this.classesArray = classesArray;
|
||||||
/** @type {!Map<string, !Documentation.Class>} */
|
/** @type {!Map<string, !Documentation.Class>} */
|
||||||
this.classes = new Map();
|
this.classes = new Map();
|
||||||
for (const cls of classesArray) this.classes.set(cls.name, cls);
|
for (const cls of classesArray) {
|
||||||
|
this.classes.set(cls.name, cls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +95,9 @@ Documentation.Member = class {
|
|||||||
this.required = required;
|
this.required = required;
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.args = new Map();
|
this.args = new Map();
|
||||||
for (const arg of argsArray) this.args.set(arg.name, arg);
|
for (const arg of argsArray) {
|
||||||
|
this.args.set(arg.name, arg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,13 +12,14 @@ function checkSources(sources) {
|
|||||||
const eventsSource = sources.find((source) => source.name() === 'Events.js');
|
const eventsSource = sources.find((source) => source.name() === 'Events.js');
|
||||||
if (eventsSource) {
|
if (eventsSource) {
|
||||||
const { Events } = require(eventsSource.filePath());
|
const { Events } = require(eventsSource.filePath());
|
||||||
for (const [className, events] of Object.entries(Events))
|
for (const [className, events] of Object.entries(Events)) {
|
||||||
classEvents.set(
|
classEvents.set(
|
||||||
className,
|
className,
|
||||||
Array.from(Object.values(events))
|
Array.from(Object.values(events))
|
||||||
.filter((e) => typeof e === 'string')
|
.filter((e) => typeof e === 'string')
|
||||||
.map((e) => Documentation.Member.createEvent(e))
|
.map((e) => Documentation.Member.createEvent(e))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const excludeClasses = new Set([]);
|
const excludeClasses = new Set([]);
|
||||||
@ -78,7 +79,9 @@ function checkSources(sources) {
|
|||||||
for (const member of wp.membersArray) {
|
for (const member of wp.membersArray) {
|
||||||
// Member was overridden.
|
// Member was overridden.
|
||||||
const memberId = member.kind + ':' + member.name;
|
const memberId = member.kind + ':' + member.name;
|
||||||
if (membersMap.has(memberId)) continue;
|
if (membersMap.has(memberId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
membersMap.set(memberId, member);
|
membersMap.set(memberId, member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,13 +101,17 @@ function checkSources(sources) {
|
|||||||
|
|
||||||
if (className === '__class') {
|
if (className === '__class') {
|
||||||
let parent = node;
|
let parent = node;
|
||||||
while (parent.parent) parent = parent.parent;
|
while (parent.parent) {
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
className = path.basename(parent.fileName, '.js');
|
className = path.basename(parent.fileName, '.js');
|
||||||
}
|
}
|
||||||
if (className && !excludeClasses.has(className)) {
|
if (className && !excludeClasses.has(className)) {
|
||||||
classes.push(serializeClass(className, symbol, node));
|
classes.push(serializeClass(className, symbol, node));
|
||||||
const parentClassName = parentClass(node);
|
const parentClassName = parentClass(node);
|
||||||
if (parentClassName) inheritance.set(className, parentClassName);
|
if (parentClassName) {
|
||||||
|
inheritance.set(className, parentClassName);
|
||||||
|
}
|
||||||
excludeClasses.add(className);
|
excludeClasses.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +146,9 @@ function checkSources(sources) {
|
|||||||
* isn't going to be here for much longer so we'll just silence this
|
* isn't going to be here for much longer so we'll just silence this
|
||||||
* warning than try to add support which would warrant a huge rewrite.
|
* warning than try to add support which would warrant a huge rewrite.
|
||||||
*/
|
*/
|
||||||
if (name !== 'paramArgs') throw error;
|
if (name !== 'paramArgs') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Documentation.Member.createProperty(
|
return Documentation.Member.createProperty(
|
||||||
@ -152,13 +161,27 @@ function checkSources(sources) {
|
|||||||
* @param {!ts.ObjectType} type
|
* @param {!ts.ObjectType} type
|
||||||
*/
|
*/
|
||||||
function isRegularObject(type) {
|
function isRegularObject(type) {
|
||||||
if (type.isIntersection()) return true;
|
if (type.isIntersection()) {
|
||||||
if (!type.objectFlags) return false;
|
return true;
|
||||||
if (!('aliasSymbol' in type)) return false;
|
}
|
||||||
if (type.getConstructSignatures().length) return false;
|
if (!type.objectFlags) {
|
||||||
if (type.getCallSignatures().length) return false;
|
return false;
|
||||||
if (type.isLiteral()) return false;
|
}
|
||||||
if (type.isUnion()) return false;
|
if (!('aliasSymbol' in type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type.getConstructSignatures().length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type.getCallSignatures().length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type.isLiteral()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type.isUnion()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -173,16 +196,18 @@ function checkSources(sources) {
|
|||||||
typeName === 'any' ||
|
typeName === 'any' ||
|
||||||
typeName === '{ [x: string]: string; }' ||
|
typeName === '{ [x: string]: string; }' ||
|
||||||
typeName === '{}'
|
typeName === '{}'
|
||||||
)
|
) {
|
||||||
typeName = 'Object';
|
typeName = 'Object';
|
||||||
|
}
|
||||||
const nextCircular = [typeName].concat(circular);
|
const nextCircular = [typeName].concat(circular);
|
||||||
|
|
||||||
if (isRegularObject(type)) {
|
if (isRegularObject(type)) {
|
||||||
let properties = undefined;
|
let properties = undefined;
|
||||||
if (!circular.includes(typeName))
|
if (!circular.includes(typeName)) {
|
||||||
properties = type
|
properties = type
|
||||||
.getProperties()
|
.getProperties()
|
||||||
.map((property) => serializeSymbol(property, nextCircular));
|
.map((property) => serializeSymbol(property, nextCircular));
|
||||||
|
}
|
||||||
return new Documentation.Type('Object', properties);
|
return new Documentation.Type('Object', properties);
|
||||||
}
|
}
|
||||||
if (type.isUnion() && typeName.includes('|')) {
|
if (type.isUnion() && typeName.includes('|')) {
|
||||||
@ -199,14 +224,17 @@ function checkSources(sources) {
|
|||||||
const innerTypeNames = [];
|
const innerTypeNames = [];
|
||||||
for (const typeArgument of type.typeArguments) {
|
for (const typeArgument of type.typeArguments) {
|
||||||
const innerType = serializeType(typeArgument, nextCircular);
|
const innerType = serializeType(typeArgument, nextCircular);
|
||||||
if (innerType.properties) properties.push(...innerType.properties);
|
if (innerType.properties) {
|
||||||
|
properties.push(...innerType.properties);
|
||||||
|
}
|
||||||
innerTypeNames.push(innerType.name);
|
innerTypeNames.push(innerType.name);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
innerTypeNames.length === 0 ||
|
innerTypeNames.length === 0 ||
|
||||||
(innerTypeNames.length === 1 && innerTypeNames[0] === 'void')
|
(innerTypeNames.length === 1 && innerTypeNames[0] === 'void')
|
||||||
)
|
) {
|
||||||
return new Documentation.Type(type.symbol.name);
|
return new Documentation.Type(type.symbol.name);
|
||||||
|
}
|
||||||
return new Documentation.Type(
|
return new Documentation.Type(
|
||||||
`${type.symbol.name}<${innerTypeNames.join(', ')}>`,
|
`${type.symbol.name}<${innerTypeNames.join(', ')}>`,
|
||||||
properties
|
properties
|
||||||
@ -241,15 +269,20 @@ function checkSources(sources) {
|
|||||||
* but in TypeScript we use the private keyword
|
* but in TypeScript we use the private keyword
|
||||||
* hence we check for either here.
|
* hence we check for either here.
|
||||||
*/
|
*/
|
||||||
if (name.startsWith('_') || symbolHasPrivateModifier(member)) continue;
|
if (name.startsWith('_') || symbolHasPrivateModifier(member)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const memberType = checker.getTypeOfSymbolAtLocation(
|
const memberType = checker.getTypeOfSymbolAtLocation(
|
||||||
member,
|
member,
|
||||||
member.valueDeclaration
|
member.valueDeclaration
|
||||||
);
|
);
|
||||||
const signature = memberType.getCallSignatures()[0];
|
const signature = memberType.getCallSignatures()[0];
|
||||||
if (signature) members.push(serializeSignature(name, signature));
|
if (signature) {
|
||||||
else members.push(serializeProperty(name, memberType));
|
members.push(serializeSignature(name, signature));
|
||||||
|
} else {
|
||||||
|
members.push(serializeProperty(name, memberType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Documentation.Class(className, members);
|
return new Documentation.Class(className, members);
|
||||||
|
@ -92,9 +92,15 @@ class MDOutline {
|
|||||||
const start = str.indexOf('<') + 1;
|
const start = str.indexOf('<') + 1;
|
||||||
let count = 1;
|
let count = 1;
|
||||||
for (let i = start; i < str.length; i++) {
|
for (let i = start; i < str.length; i++) {
|
||||||
if (str[i] === '<') count++;
|
if (str[i] === '<') {
|
||||||
if (str[i] === '>') count--;
|
count++;
|
||||||
if (!count) return str.substring(start, i);
|
}
|
||||||
|
if (str[i] === '>') {
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
if (!count) {
|
||||||
|
return str.substring(start, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
@ -138,7 +144,7 @@ class MDOutline {
|
|||||||
* @param {Node} content
|
* @param {Node} content
|
||||||
*/
|
*/
|
||||||
function parseComment(content) {
|
function parseComment(content) {
|
||||||
for (const code of content.querySelectorAll('pre > code'))
|
for (const code of content.querySelectorAll('pre > code')) {
|
||||||
code.replaceWith(
|
code.replaceWith(
|
||||||
'```' +
|
'```' +
|
||||||
code.className.substring('language-'.length) +
|
code.className.substring('language-'.length) +
|
||||||
@ -146,10 +152,13 @@ class MDOutline {
|
|||||||
code.textContent +
|
code.textContent +
|
||||||
'```'
|
'```'
|
||||||
);
|
);
|
||||||
for (const code of content.querySelectorAll('code'))
|
}
|
||||||
|
for (const code of content.querySelectorAll('code')) {
|
||||||
code.replaceWith('`' + code.textContent + '`');
|
code.replaceWith('`' + code.textContent + '`');
|
||||||
for (const strong of content.querySelectorAll('strong'))
|
}
|
||||||
|
for (const strong of content.querySelectorAll('strong')) {
|
||||||
strong.replaceWith('**' + parseComment(strong) + '**');
|
strong.replaceWith('**' + parseComment(strong) + '**');
|
||||||
|
}
|
||||||
return content.textContent.trim();
|
return content.textContent.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,10 +216,11 @@ class MDOutline {
|
|||||||
0,
|
0,
|
||||||
Math.min(angleIndex, spaceIndex)
|
Math.min(angleIndex, spaceIndex)
|
||||||
);
|
);
|
||||||
if (actualText !== expectedText)
|
if (actualText !== expectedText) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`
|
`${name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const comment = parseComment(
|
const comment = parseComment(
|
||||||
@ -257,7 +267,9 @@ class MDOutline {
|
|||||||
let currentClassExtends = null;
|
let currentClassExtends = null;
|
||||||
for (const cls of classes) {
|
for (const cls of classes) {
|
||||||
const match = cls.name.match(classHeading);
|
const match = cls.name.match(classHeading);
|
||||||
if (!match) continue;
|
if (!match) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
currentClassName = match[1];
|
currentClassName = match[1];
|
||||||
currentClassComment = cls.comment;
|
currentClassComment = cls.comment;
|
||||||
currentClassExtends = cls.extendsName;
|
currentClassExtends = cls.extendsName;
|
||||||
@ -290,7 +302,7 @@ class MDOutline {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
||||||
if (parameters !== member.args.map((arg) => arg.name).join(', '))
|
if (parameters !== member.args.map((arg) => arg.name).join(', ')) {
|
||||||
this.errors.push(
|
this.errors.push(
|
||||||
`Heading arguments for "${
|
`Heading arguments for "${
|
||||||
member.name
|
member.name
|
||||||
@ -298,6 +310,7 @@ class MDOutline {
|
|||||||
.map((a) => a.name)
|
.map((a) => a.name)
|
||||||
.join(', ')}"`
|
.join(', ')}"`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const args = member.args.map(createPropertyFromJSON);
|
const args = member.args.map(createPropertyFromJSON);
|
||||||
let returnType = null;
|
let returnType = null;
|
||||||
let returnComment = '';
|
let returnComment = '';
|
||||||
@ -369,7 +382,9 @@ class MDOutline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flushClassIfNeeded() {
|
function flushClassIfNeeded() {
|
||||||
if (currentClassName === null) return;
|
if (currentClassName === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.classes.push(
|
this.classes.push(
|
||||||
new Documentation.Class(
|
new Documentation.Class(
|
||||||
currentClassName,
|
currentClassName,
|
||||||
|
@ -78,46 +78,62 @@ function checkSorting(doc) {
|
|||||||
;
|
;
|
||||||
eventIndex < members.length && members[eventIndex].kind === 'event';
|
eventIndex < members.length && members[eventIndex].kind === 'event';
|
||||||
++eventIndex
|
++eventIndex
|
||||||
);
|
) {}
|
||||||
for (
|
for (
|
||||||
;
|
;
|
||||||
eventIndex < members.length && members[eventIndex].kind !== 'event';
|
eventIndex < members.length && members[eventIndex].kind !== 'event';
|
||||||
++eventIndex
|
++eventIndex
|
||||||
);
|
) {}
|
||||||
if (eventIndex < members.length)
|
if (eventIndex < members.length) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`
|
`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor should be right after events and before all other members.
|
// Constructor should be right after events and before all other members.
|
||||||
const constructorIndex = members.findIndex(
|
const constructorIndex = members.findIndex(
|
||||||
(member) => member.kind === 'method' && member.name === 'constructor'
|
(member) => member.kind === 'method' && member.name === 'constructor'
|
||||||
);
|
);
|
||||||
if (constructorIndex > 0 && members[constructorIndex - 1].kind !== 'event')
|
if (
|
||||||
|
constructorIndex > 0 &&
|
||||||
|
members[constructorIndex - 1].kind !== 'event'
|
||||||
|
) {
|
||||||
errors.push(`Constructor of ${cls.name} should go before other methods`);
|
errors.push(`Constructor of ${cls.name} should go before other methods`);
|
||||||
|
}
|
||||||
|
|
||||||
// Events should be sorted alphabetically.
|
// Events should be sorted alphabetically.
|
||||||
for (let i = 0; i < members.length - 1; ++i) {
|
for (let i = 0; i < members.length - 1; ++i) {
|
||||||
const member1 = cls.membersArray[i];
|
const member1 = cls.membersArray[i];
|
||||||
const member2 = cls.membersArray[i + 1];
|
const member2 = cls.membersArray[i + 1];
|
||||||
if (member1.kind !== 'event' || member2.kind !== 'event') continue;
|
if (member1.kind !== 'event' || member2.kind !== 'event') {
|
||||||
if (member1.name > member2.name)
|
continue;
|
||||||
|
}
|
||||||
|
if (member1.name > member2.name) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`
|
`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other members should be sorted alphabetically.
|
// All other members should be sorted alphabetically.
|
||||||
for (let i = 0; i < members.length - 1; ++i) {
|
for (let i = 0; i < members.length - 1; ++i) {
|
||||||
const member1 = cls.membersArray[i];
|
const member1 = cls.membersArray[i];
|
||||||
const member2 = cls.membersArray[i + 1];
|
const member2 = cls.membersArray[i + 1];
|
||||||
if (member1.kind === 'event' || member2.kind === 'event') continue;
|
if (member1.kind === 'event' || member2.kind === 'event') {
|
||||||
if (member1.kind === 'method' && member1.name === 'constructor') continue;
|
continue;
|
||||||
|
}
|
||||||
|
if (member1.kind === 'method' && member1.name === 'constructor') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (member1.name > member2.name) {
|
if (member1.name > member2.name) {
|
||||||
let memberName1 = `${cls.name}.${member1.name}`;
|
let memberName1 = `${cls.name}.${member1.name}`;
|
||||||
if (member1.kind === 'method') memberName1 += '()';
|
if (member1.kind === 'method') {
|
||||||
|
memberName1 += '()';
|
||||||
|
}
|
||||||
let memberName2 = `${cls.name}.${member2.name}`;
|
let memberName2 = `${cls.name}.${member2.name}`;
|
||||||
if (member2.kind === 'method') memberName2 += '()';
|
if (member2.kind === 'method') {
|
||||||
|
memberName2 += '()';
|
||||||
|
}
|
||||||
errors.push(
|
errors.push(
|
||||||
`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`
|
`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`
|
||||||
);
|
);
|
||||||
@ -136,7 +152,9 @@ function filterJSDocumentation(jsDocumentation) {
|
|||||||
// Filter private classes and methods.
|
// Filter private classes and methods.
|
||||||
const classes = [];
|
const classes = [];
|
||||||
for (const cls of jsDocumentation.classesArray) {
|
for (const cls of jsDocumentation.classesArray) {
|
||||||
if (includedClasses && !includedClasses.has(cls.name)) continue;
|
if (includedClasses && !includedClasses.has(cls.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const members = cls.membersArray.filter(
|
const members = cls.membersArray.filter(
|
||||||
(member) => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`)
|
(member) => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`)
|
||||||
);
|
);
|
||||||
@ -154,22 +172,25 @@ function checkDuplicates(doc) {
|
|||||||
const classes = new Set();
|
const classes = new Set();
|
||||||
// Report duplicates.
|
// Report duplicates.
|
||||||
for (const cls of doc.classesArray) {
|
for (const cls of doc.classesArray) {
|
||||||
if (classes.has(cls.name))
|
if (classes.has(cls.name)) {
|
||||||
errors.push(`Duplicate declaration of class ${cls.name}`);
|
errors.push(`Duplicate declaration of class ${cls.name}`);
|
||||||
|
}
|
||||||
classes.add(cls.name);
|
classes.add(cls.name);
|
||||||
const members = new Set();
|
const members = new Set();
|
||||||
for (const member of cls.membersArray) {
|
for (const member of cls.membersArray) {
|
||||||
if (members.has(member.kind + ' ' + member.name))
|
if (members.has(member.kind + ' ' + member.name)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Duplicate declaration of ${member.kind} ${cls.name}.${member.name}()`
|
`Duplicate declaration of ${member.kind} ${cls.name}.${member.name}()`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
members.add(member.kind + ' ' + member.name);
|
members.add(member.kind + ' ' + member.name);
|
||||||
const args = new Set();
|
const args = new Set();
|
||||||
for (const arg of member.argsArray) {
|
for (const arg of member.argsArray) {
|
||||||
if (args.has(arg.name))
|
if (args.has(arg.name)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Duplicate declaration of argument ${cls.name}.${member.name} "${arg.name}"`
|
`Duplicate declaration of argument ${cls.name}.${member.name} "${arg.name}"`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
args.add(arg.name);
|
args.add(arg.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,8 +241,9 @@ function compareDocumentations(actual, expected) {
|
|||||||
'launch',
|
'launch',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (const className of classesDiff.extra)
|
for (const className of classesDiff.extra) {
|
||||||
errors.push(`Non-existing class found: ${className}`);
|
errors.push(`Non-existing class found: ${className}`);
|
||||||
|
}
|
||||||
|
|
||||||
for (const className of classesDiff.missing) {
|
for (const className of classesDiff.missing) {
|
||||||
if (className === 'PuppeteerNode') {
|
if (className === 'PuppeteerNode') {
|
||||||
@ -249,8 +271,9 @@ function compareDocumentations(actual, expected) {
|
|||||||
|
|
||||||
for (const methodName of methodDiff.missing) {
|
for (const methodName of methodDiff.missing) {
|
||||||
const missingMethodsForClass = expectedNotFoundMethods.get(className);
|
const missingMethodsForClass = expectedNotFoundMethods.get(className);
|
||||||
if (missingMethodsForClass && missingMethodsForClass.has(methodName))
|
if (missingMethodsForClass && missingMethodsForClass.has(methodName)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
errors.push(`Method not found: ${className}.${methodName}()`);
|
errors.push(`Method not found: ${className}.${methodName}()`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,14 +281,15 @@ function compareDocumentations(actual, expected) {
|
|||||||
const actualMethod = actualClass.methods.get(methodName);
|
const actualMethod = actualClass.methods.get(methodName);
|
||||||
const expectedMethod = expectedClass.methods.get(methodName);
|
const expectedMethod = expectedClass.methods.get(methodName);
|
||||||
if (!actualMethod.type !== !expectedMethod.type) {
|
if (!actualMethod.type !== !expectedMethod.type) {
|
||||||
if (actualMethod.type)
|
if (actualMethod.type) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Method ${className}.${methodName} has unneeded description of return type`
|
`Method ${className}.${methodName} has unneeded description of return type`
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Method ${className}.${methodName} is missing return type description`
|
`Method ${className}.${methodName} is missing return type description`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (actualMethod.hasReturn) {
|
} else if (actualMethod.hasReturn) {
|
||||||
checkType(
|
checkType(
|
||||||
`Method ${className}.${methodName} has the wrong return type: `,
|
`Method ${className}.${methodName} has the wrong return type: `,
|
||||||
@ -287,20 +311,23 @@ function compareDocumentations(actual, expected) {
|
|||||||
const text = [
|
const text = [
|
||||||
`Method ${className}.${methodName}() fails to describe its parameters:`,
|
`Method ${className}.${methodName}() fails to describe its parameters:`,
|
||||||
];
|
];
|
||||||
for (const arg of argsDiff.missing)
|
for (const arg of argsDiff.missing) {
|
||||||
text.push(`- Argument not found: ${arg}`);
|
text.push(`- Argument not found: ${arg}`);
|
||||||
for (const arg of argsDiff.extra)
|
}
|
||||||
|
for (const arg of argsDiff.extra) {
|
||||||
text.push(`- Non-existing argument found: ${arg}`);
|
text.push(`- Non-existing argument found: ${arg}`);
|
||||||
|
}
|
||||||
errors.push(text.join('\n'));
|
errors.push(text.join('\n'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const arg of argsDiff.equal)
|
for (const arg of argsDiff.equal) {
|
||||||
checkProperty(
|
checkProperty(
|
||||||
`Method ${className}.${methodName}()`,
|
`Method ${className}.${methodName}()`,
|
||||||
actualMethod.args.get(arg),
|
actualMethod.args.get(arg),
|
||||||
expectedMethod.args.get(arg)
|
expectedMethod.args.get(arg)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const actualProperties = Array.from(actualClass.properties.keys()).sort();
|
const actualProperties = Array.from(actualClass.properties.keys()).sort();
|
||||||
const expectedProperties = Array.from(
|
const expectedProperties = Array.from(
|
||||||
@ -313,18 +340,21 @@ function compareDocumentations(actual, expected) {
|
|||||||
}
|
}
|
||||||
errors.push(`Non-existing property found: ${className}.${propertyName}`);
|
errors.push(`Non-existing property found: ${className}.${propertyName}`);
|
||||||
}
|
}
|
||||||
for (const propertyName of propertyDiff.missing)
|
for (const propertyName of propertyDiff.missing) {
|
||||||
errors.push(`Property not found: ${className}.${propertyName}`);
|
errors.push(`Property not found: ${className}.${propertyName}`);
|
||||||
|
}
|
||||||
|
|
||||||
const actualEvents = Array.from(actualClass.events.keys()).sort();
|
const actualEvents = Array.from(actualClass.events.keys()).sort();
|
||||||
const expectedEvents = Array.from(expectedClass.events.keys()).sort();
|
const expectedEvents = Array.from(expectedClass.events.keys()).sort();
|
||||||
const eventsDiff = diff(actualEvents, expectedEvents);
|
const eventsDiff = diff(actualEvents, expectedEvents);
|
||||||
for (const eventName of eventsDiff.extra)
|
for (const eventName of eventsDiff.extra) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Non-existing event found in class ${className}: '${eventName}'`
|
`Non-existing event found in class ${className}: '${eventName}'`
|
||||||
);
|
);
|
||||||
for (const eventName of eventsDiff.missing)
|
}
|
||||||
|
for (const eventName of eventsDiff.missing) {
|
||||||
errors.push(`Event not found in class ${className}: '${eventName}'`);
|
errors.push(`Event not found in class ${className}: '${eventName}'`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1052,7 +1082,9 @@ function compareDocumentations(actual, expected) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const expectedForSource = expectedNamingMismatches.get(source);
|
const expectedForSource = expectedNamingMismatches.get(source);
|
||||||
if (!expectedForSource) return false;
|
if (!expectedForSource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const namingMismatchIsExpected =
|
const namingMismatchIsExpected =
|
||||||
expectedForSource.actualName === actualName &&
|
expectedForSource.actualName === actualName &&
|
||||||
@ -1068,8 +1100,12 @@ function compareDocumentations(actual, expected) {
|
|||||||
*/
|
*/
|
||||||
function checkType(source, actual, expected) {
|
function checkType(source, actual, expected) {
|
||||||
// TODO(@JoelEinbinder): check functions and Serializable
|
// TODO(@JoelEinbinder): check functions and Serializable
|
||||||
if (actual.name.includes('unction') || actual.name.includes('Serializable'))
|
if (
|
||||||
|
actual.name.includes('unction') ||
|
||||||
|
actual.name.includes('Serializable')
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
// We don't have nullchecks on for TypeScript
|
// We don't have nullchecks on for TypeScript
|
||||||
const actualName = actual.name.replace(/[\? ]/g, '');
|
const actualName = actual.name.replace(/[\? ]/g, '');
|
||||||
// TypeScript likes to add some spaces
|
// TypeScript likes to add some spaces
|
||||||
@ -1079,13 +1115,16 @@ function compareDocumentations(actual, expected) {
|
|||||||
actualName,
|
actualName,
|
||||||
expectedName
|
expectedName
|
||||||
);
|
);
|
||||||
if (expectedName !== actualName && !namingMismatchIsExpected)
|
if (expectedName !== actualName && !namingMismatchIsExpected) {
|
||||||
errors.push(`${source} ${actualName} != ${expectedName}`);
|
errors.push(`${source} ${actualName} != ${expectedName}`);
|
||||||
|
}
|
||||||
|
|
||||||
/* If we got a naming mismatch and it was expected, don't check the properties
|
/* If we got a naming mismatch and it was expected, don't check the properties
|
||||||
* as they will likely be considered "wrong" by DocLint too.
|
* as they will likely be considered "wrong" by DocLint too.
|
||||||
*/
|
*/
|
||||||
if (namingMismatchIsExpected) return;
|
if (namingMismatchIsExpected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Some methods cause errors in the property checks for an unknown reason
|
/* Some methods cause errors in the property checks for an unknown reason
|
||||||
* so we support a list of methods whose parameters are not checked.
|
* so we support a list of methods whose parameters are not checked.
|
||||||
@ -1096,7 +1135,9 @@ function compareDocumentations(actual, expected) {
|
|||||||
'Method Puppeteer.connect() options',
|
'Method Puppeteer.connect() options',
|
||||||
'Method Page.setUserAgent() userAgentMetadata',
|
'Method Page.setUserAgent() userAgentMetadata',
|
||||||
]);
|
]);
|
||||||
if (skipPropertyChecksOnMethods.has(source)) return;
|
if (skipPropertyChecksOnMethods.has(source)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const actualPropertiesMap = new Map(
|
const actualPropertiesMap = new Map(
|
||||||
actual.properties.map((property) => [property.name, property.type])
|
actual.properties.map((property) => [property.name, property.type])
|
||||||
@ -1108,16 +1149,19 @@ function compareDocumentations(actual, expected) {
|
|||||||
Array.from(actualPropertiesMap.keys()).sort(),
|
Array.from(actualPropertiesMap.keys()).sort(),
|
||||||
Array.from(expectedPropertiesMap.keys()).sort()
|
Array.from(expectedPropertiesMap.keys()).sort()
|
||||||
);
|
);
|
||||||
for (const propertyName of propertiesDiff.extra)
|
for (const propertyName of propertiesDiff.extra) {
|
||||||
errors.push(`${source} has unexpected property ${propertyName}`);
|
errors.push(`${source} has unexpected property ${propertyName}`);
|
||||||
for (const propertyName of propertiesDiff.missing)
|
}
|
||||||
|
for (const propertyName of propertiesDiff.missing) {
|
||||||
errors.push(`${source} is missing property ${propertyName}`);
|
errors.push(`${source} is missing property ${propertyName}`);
|
||||||
for (const propertyName of propertiesDiff.equal)
|
}
|
||||||
|
for (const propertyName of propertiesDiff.equal) {
|
||||||
checkType(
|
checkType(
|
||||||
source + '.' + propertyName,
|
source + '.' + propertyName,
|
||||||
actualPropertiesMap.get(propertyName),
|
actualPropertiesMap.get(propertyName),
|
||||||
expectedPropertiesMap.get(propertyName)
|
expectedPropertiesMap.get(propertyName)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
@ -1131,9 +1175,15 @@ function compareDocumentations(actual, expected) {
|
|||||||
function diff(actual, expected) {
|
function diff(actual, expected) {
|
||||||
const N = actual.length;
|
const N = actual.length;
|
||||||
const M = expected.length;
|
const M = expected.length;
|
||||||
if (N === 0 && M === 0) return { extra: [], missing: [], equal: [] };
|
if (N === 0 && M === 0) {
|
||||||
if (N === 0) return { extra: [], missing: expected.slice(), equal: [] };
|
return { extra: [], missing: [], equal: [] };
|
||||||
if (M === 0) return { extra: actual.slice(), missing: [], equal: [] };
|
}
|
||||||
|
if (N === 0) {
|
||||||
|
return { extra: [], missing: expected.slice(), equal: [] };
|
||||||
|
}
|
||||||
|
if (M === 0) {
|
||||||
|
return { extra: actual.slice(), missing: [], equal: [] };
|
||||||
|
}
|
||||||
const d = new Array(N);
|
const d = new Array(N);
|
||||||
const bt = new Array(N);
|
const bt = new Array(N);
|
||||||
for (let i = 0; i < N; ++i) {
|
for (let i = 0; i < N; ++i) {
|
||||||
@ -1179,8 +1229,12 @@ function diff(actual, expected) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (i >= 0) extra.push(actual[i--]);
|
while (i >= 0) {
|
||||||
while (j >= 0) missing.push(expected[j--]);
|
extra.push(actual[i--]);
|
||||||
|
}
|
||||||
|
while (j >= 0) {
|
||||||
|
missing.push(expected[j--]);
|
||||||
|
}
|
||||||
extra.reverse();
|
extra.reverse();
|
||||||
missing.reverse();
|
missing.reverse();
|
||||||
equal.reverse();
|
equal.reverse();
|
||||||
|
@ -105,7 +105,9 @@ async function run() {
|
|||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
for (const source of mdSources) {
|
for (const source of mdSources) {
|
||||||
if (!source.hasUpdatedText()) continue;
|
if (!source.hasUpdatedText()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await source.save();
|
await source.save();
|
||||||
changedFiles = true;
|
changedFiles = true;
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,9 @@ module.exports.ensureReleasedAPILinks = function (
|
|||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
const text = source.text();
|
const text = source.text();
|
||||||
const newText = text.replace(apiLinkRegex, lastReleasedAPI);
|
const newText = text.replace(apiLinkRegex, lastReleasedAPI);
|
||||||
if (source.setText(newText))
|
if (source.setText(newText)) {
|
||||||
messages.push(Message.info(`GEN: updated ${source.projectPath()}`));
|
messages.push(Message.info(`GEN: updated ${source.projectPath()}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
};
|
};
|
||||||
@ -121,7 +122,9 @@ function generateTableOfContents(mdText) {
|
|||||||
insideCodeBlock = !insideCodeBlock;
|
insideCodeBlock = !insideCodeBlock;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!insideCodeBlock && line.startsWith('#')) titles.push(line);
|
if (!insideCodeBlock && line.startsWith('#')) {
|
||||||
|
titles.push(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const tocEntries = [];
|
const tocEntries = [];
|
||||||
for (const title of titles) {
|
for (const title of titles) {
|
||||||
@ -134,7 +137,9 @@ function generateTableOfContents(mdText) {
|
|||||||
.replace(/[^-0-9a-zа-яё]/gi, '');
|
.replace(/[^-0-9a-zа-яё]/gi, '');
|
||||||
let dedupId = id;
|
let dedupId = id;
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
while (ids.has(dedupId)) dedupId = id + '-' + ++counter;
|
while (ids.has(dedupId)) {
|
||||||
|
dedupId = id + '-' + ++counter;
|
||||||
|
}
|
||||||
ids.add(dedupId);
|
ids.add(dedupId);
|
||||||
tocEntries.push({
|
tocEntries.push({
|
||||||
level: nesting.length,
|
level: nesting.length,
|
||||||
@ -162,7 +167,9 @@ const generateVersionsPerRelease = () => {
|
|||||||
const { versionsPerRelease } = require('../../../versions.js');
|
const { versionsPerRelease } = require('../../../versions.js');
|
||||||
const buffer = ['- Releases per Chromium version:'];
|
const buffer = ['- Releases per Chromium version:'];
|
||||||
for (const [chromiumVersion, puppeteerVersion] of versionsPerRelease) {
|
for (const [chromiumVersion, puppeteerVersion] of versionsPerRelease) {
|
||||||
if (puppeteerVersion === 'NEXT') continue;
|
if (puppeteerVersion === 'NEXT') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
buffer.push(
|
buffer.push(
|
||||||
` * Chromium ${chromiumVersion} - [Puppeteer ${puppeteerVersion}](https://github.com/puppeteer/puppeteer/blob/${puppeteerVersion}/docs/api.md)`
|
` * Chromium ${chromiumVersion} - [Puppeteer ${puppeteerVersion}](https://github.com/puppeteer/puppeteer/blob/${puppeteerVersion}/docs/api.md)`
|
||||||
);
|
);
|
||||||
|
@ -91,12 +91,15 @@ async function main(url) {
|
|||||||
let devices = [];
|
let devices = [];
|
||||||
for (const payload of devicePayloads) {
|
for (const payload of devicePayloads) {
|
||||||
let names = [];
|
let names = [];
|
||||||
if (payload.title === 'iPhone 6/7/8')
|
if (payload.title === 'iPhone 6/7/8') {
|
||||||
names = ['iPhone 6', 'iPhone 7', 'iPhone 8'];
|
names = ['iPhone 6', 'iPhone 7', 'iPhone 8'];
|
||||||
else if (payload.title === 'iPhone 6/7/8 Plus')
|
} else if (payload.title === 'iPhone 6/7/8 Plus') {
|
||||||
names = ['iPhone 6 Plus', 'iPhone 7 Plus', 'iPhone 8 Plus'];
|
names = ['iPhone 6 Plus', 'iPhone 7 Plus', 'iPhone 8 Plus'];
|
||||||
else if (payload.title === 'iPhone 5/SE') names = ['iPhone 5', 'iPhone SE'];
|
} else if (payload.title === 'iPhone 5/SE') {
|
||||||
else names = [payload.title];
|
names = ['iPhone 5', 'iPhone SE'];
|
||||||
|
} else {
|
||||||
|
names = [payload.title];
|
||||||
|
}
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
const device = createDevice(chromeVersion, name, payload, false);
|
const device = createDevice(chromeVersion, name, payload, false);
|
||||||
const landscape = createDevice(chromeVersion, name, payload, true);
|
const landscape = createDevice(chromeVersion, name, payload, true);
|
||||||
@ -104,8 +107,9 @@ async function main(url) {
|
|||||||
if (
|
if (
|
||||||
landscape.viewport.width !== device.viewport.width ||
|
landscape.viewport.width !== device.viewport.width ||
|
||||||
landscape.viewport.height !== device.viewport.height
|
landscape.viewport.height !== device.viewport.height
|
||||||
)
|
) {
|
||||||
devices.push(landscape);
|
devices.push(landscape);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
devices = devices.filter((device) => device.viewport.isMobile);
|
devices = devices.filter((device) => device.viewport.isMobile);
|
||||||
@ -164,13 +168,15 @@ function loadFromJSONV1(json) {
|
|||||||
object === null ||
|
object === null ||
|
||||||
!object.hasOwnProperty(key)
|
!object.hasOwnProperty(key)
|
||||||
) {
|
) {
|
||||||
if (typeof defaultValue !== 'undefined') return defaultValue;
|
if (typeof defaultValue !== 'undefined') {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Emulated device is missing required property '" + key + "'"
|
"Emulated device is missing required property '" + key + "'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const value = object[key];
|
const value = object[key];
|
||||||
if (typeof value !== type || value === null)
|
if (typeof value !== type || value === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Emulated device property '" +
|
"Emulated device property '" +
|
||||||
key +
|
key +
|
||||||
@ -178,6 +184,7 @@ function loadFromJSONV1(json) {
|
|||||||
typeof value +
|
typeof value +
|
||||||
"'"
|
"'"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,8 +195,9 @@ function loadFromJSONV1(json) {
|
|||||||
*/
|
*/
|
||||||
function parseIntValue(object, key) {
|
function parseIntValue(object, key) {
|
||||||
const value = /** @type {number} */ (parseValue(object, key, 'number'));
|
const value = /** @type {number} */ (parseValue(object, key, 'number'));
|
||||||
if (value !== Math.abs(value))
|
if (value !== Math.abs(value)) {
|
||||||
throw new Error("Emulated device value '" + key + "' must be integer");
|
throw new Error("Emulated device value '" + key + "' must be integer");
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,16 +214,18 @@ function loadFromJSONV1(json) {
|
|||||||
result.width < 0 ||
|
result.width < 0 ||
|
||||||
result.width > maxDeviceSize ||
|
result.width > maxDeviceSize ||
|
||||||
result.width < minDeviceSize
|
result.width < minDeviceSize
|
||||||
)
|
) {
|
||||||
throw new Error('Emulated device has wrong width: ' + result.width);
|
throw new Error('Emulated device has wrong width: ' + result.width);
|
||||||
|
}
|
||||||
|
|
||||||
result.height = parseIntValue(json, 'height');
|
result.height = parseIntValue(json, 'height');
|
||||||
if (
|
if (
|
||||||
result.height < 0 ||
|
result.height < 0 ||
|
||||||
result.height > maxDeviceSize ||
|
result.height > maxDeviceSize ||
|
||||||
result.height < minDeviceSize
|
result.height < minDeviceSize
|
||||||
)
|
) {
|
||||||
throw new Error('Emulated device has wrong height: ' + result.height);
|
throw new Error('Emulated device has wrong height: ' + result.height);
|
||||||
|
}
|
||||||
|
|
||||||
return /** @type {!{width: number, height: number}} */ (result);
|
return /** @type {!{width: number, height: number}} */ (result);
|
||||||
}
|
}
|
||||||
@ -227,22 +237,25 @@ function loadFromJSONV1(json) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const capabilities = parseValue(json, 'capabilities', 'object', []);
|
const capabilities = parseValue(json, 'capabilities', 'object', []);
|
||||||
if (!Array.isArray(capabilities))
|
if (!Array.isArray(capabilities)) {
|
||||||
throw new Error('Emulated device capabilities must be an array');
|
throw new Error('Emulated device capabilities must be an array');
|
||||||
|
}
|
||||||
result.capabilities = [];
|
result.capabilities = [];
|
||||||
for (let i = 0; i < capabilities.length; ++i) {
|
for (let i = 0; i < capabilities.length; ++i) {
|
||||||
if (typeof capabilities[i] !== 'string')
|
if (typeof capabilities[i] !== 'string') {
|
||||||
throw new Error('Emulated device capability must be a string');
|
throw new Error('Emulated device capability must be a string');
|
||||||
|
}
|
||||||
result.capabilities.push(capabilities[i]);
|
result.capabilities.push(capabilities[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.deviceScaleFactor = /** @type {number} */ (
|
result.deviceScaleFactor = /** @type {number} */ (
|
||||||
parseValue(json['screen'], 'device-pixel-ratio', 'number')
|
parseValue(json['screen'], 'device-pixel-ratio', 'number')
|
||||||
);
|
);
|
||||||
if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100)
|
if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor
|
'Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
result.vertical = parseOrientation(
|
result.vertical = parseOrientation(
|
||||||
parseValue(json['screen'], 'vertical', 'object')
|
parseValue(json['screen'], 'vertical', 'object')
|
||||||
|
@ -69,9 +69,11 @@ class TestServer {
|
|||||||
* @param {!Object=} sslOptions
|
* @param {!Object=} sslOptions
|
||||||
*/
|
*/
|
||||||
constructor(dirPath, port, sslOptions) {
|
constructor(dirPath, port, sslOptions) {
|
||||||
if (sslOptions)
|
if (sslOptions) {
|
||||||
this._server = https.createServer(sslOptions, this._onRequest.bind(this));
|
this._server = https.createServer(sslOptions, this._onRequest.bind(this));
|
||||||
else this._server = http.createServer(this._onRequest.bind(this));
|
} else {
|
||||||
|
this._server = http.createServer(this._onRequest.bind(this));
|
||||||
|
}
|
||||||
this._server.on('connection', (socket) => this._onSocket(socket));
|
this._server.on('connection', (socket) => this._onSocket(socket));
|
||||||
this._wsServer = new WebSocketServer({ server: this._server });
|
this._wsServer = new WebSocketServer({ server: this._server });
|
||||||
this._wsServer.on('connection', this._onWebSocketConnection.bind(this));
|
this._wsServer.on('connection', this._onWebSocketConnection.bind(this));
|
||||||
@ -101,7 +103,9 @@ class TestServer {
|
|||||||
// ECONNRESET is a legit error given
|
// ECONNRESET is a legit error given
|
||||||
// that tab closing simply kills process.
|
// that tab closing simply kills process.
|
||||||
socket.on('error', (error) => {
|
socket.on('error', (error) => {
|
||||||
if (error.code !== 'ECONNRESET') throw error;
|
if (error.code !== 'ECONNRESET') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
socket.once('close', () => this._sockets.delete(socket));
|
socket.once('close', () => this._sockets.delete(socket));
|
||||||
}
|
}
|
||||||
@ -136,7 +140,9 @@ class TestServer {
|
|||||||
|
|
||||||
async stop() {
|
async stop() {
|
||||||
this.reset();
|
this.reset();
|
||||||
for (const socket of this._sockets) socket.destroy();
|
for (const socket of this._sockets) {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
this._sockets.clear();
|
this._sockets.clear();
|
||||||
await new Promise((x) => this._server.close(x));
|
await new Promise((x) => this._server.close(x));
|
||||||
}
|
}
|
||||||
@ -166,7 +172,9 @@ class TestServer {
|
|||||||
*/
|
*/
|
||||||
waitForRequest(path) {
|
waitForRequest(path) {
|
||||||
let promise = this._requestSubscribers.get(path);
|
let promise = this._requestSubscribers.get(path);
|
||||||
if (promise) return promise;
|
if (promise) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
let fulfill, reject;
|
let fulfill, reject;
|
||||||
promise = new Promise((f, r) => {
|
promise = new Promise((f, r) => {
|
||||||
fulfill = f;
|
fulfill = f;
|
||||||
@ -184,15 +192,19 @@ class TestServer {
|
|||||||
this._csp.clear();
|
this._csp.clear();
|
||||||
this._gzipRoutes.clear();
|
this._gzipRoutes.clear();
|
||||||
const error = new Error('Static Server has been reset');
|
const error = new Error('Static Server has been reset');
|
||||||
for (const subscriber of this._requestSubscribers.values())
|
for (const subscriber of this._requestSubscribers.values()) {
|
||||||
subscriber[rejectSymbol].call(null, error);
|
subscriber[rejectSymbol].call(null, error);
|
||||||
|
}
|
||||||
this._requestSubscribers.clear();
|
this._requestSubscribers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRequest(request, response) {
|
_onRequest(request, response) {
|
||||||
request.on('error', (error) => {
|
request.on('error', (error) => {
|
||||||
if (error.code === 'ECONNRESET') response.end();
|
if (error.code === 'ECONNRESET') {
|
||||||
else throw error;
|
response.end();
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
request.postBody = new Promise((resolve) => {
|
request.postBody = new Promise((resolve) => {
|
||||||
let body = '';
|
let body = '';
|
||||||
@ -234,7 +246,9 @@ class TestServer {
|
|||||||
* @param {string} pathName
|
* @param {string} pathName
|
||||||
*/
|
*/
|
||||||
serveFile(request, response, pathName) {
|
serveFile(request, response, pathName) {
|
||||||
if (pathName === '/') pathName = '/index.html';
|
if (pathName === '/') {
|
||||||
|
pathName = '/index.html';
|
||||||
|
}
|
||||||
const filePath = path.join(this._dirPath, pathName.substring(1));
|
const filePath = path.join(this._dirPath, pathName.substring(1));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -251,8 +265,9 @@ class TestServer {
|
|||||||
} else {
|
} else {
|
||||||
response.setHeader('Cache-Control', 'no-cache, no-store');
|
response.setHeader('Cache-Control', 'no-cache, no-store');
|
||||||
}
|
}
|
||||||
if (this._csp.has(pathName))
|
if (this._csp.has(pathName)) {
|
||||||
response.setHeader('Content-Security-Policy', this._csp.get(pathName));
|
response.setHeader('Content-Security-Policy', this._csp.get(pathName));
|
||||||
|
}
|
||||||
|
|
||||||
fs.readFile(filePath, (err, data) => {
|
fs.readFile(filePath, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
Loading…
Reference in New Issue
Block a user