chore: use curly (#8519)

This commit is contained in:
jrandolf 2022-06-14 13:55:35 +02:00 committed by GitHub
parent 0678343b53
commit e6442dd767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 1507 additions and 590 deletions

View File

@ -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,

View File

@ -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 });

View File

@ -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;
}

View File

@ -76,8 +76,9 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
return '';
}
);
if (defaultName && !queryOptions.name)
if (defaultName && !queryOptions.name) {
queryOptions.name = normalizeValue(defaultName);
}
return queryOptions;
}

View File

@ -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', {

View File

@ -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', () => {});

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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 (

View File

@ -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);
}

View File

@ -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.

View File

@ -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,

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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('-'))

View File

@ -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);
}
};

View File

@ -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);
}
}
}

View File

@ -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,

View File

@ -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:'

View File

@ -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);
}
});

View File

@ -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(

View File

@ -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', () => {});

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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));}
}
})();

View File

@ -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');
}
});
});

View File

@ -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();
});

View File

@ -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);
});
}

View File

@ -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();

View File

@ -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);
});
});

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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');
}
});
});

View File

@ -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);
}
});
});

View File

@ -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();
}

View File

@ -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(

View File

@ -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 () => {

View File

@ -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([

View File

@ -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 () => {

View File

@ -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');

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 () => {

View File

@ -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();
});

View File

@ -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);
});

View File

@ -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;
});
});

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}
/**

View File

@ -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);

View File

@ -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,

View File

@ -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();

View File

@ -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;
}

View File

@ -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)`
);

View File

@ -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')

View File

@ -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) {