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