chore: use strict typing in tests (#8524)

* The testing tsconfig.json inherits from the base TS config.
  * A lot of type assertions have been inserted...a lot.
* All testing utilities have migrated to TS.
* text-diff is being replaced with diff for TS compatibility.
* ProtocolError has been added to PuppeteerErrors and PuppeteerErrors is no longer a record (it's been frozen).
* Fixes a small bug where null was an allowable media type in emulation (should be undefined).
This commit is contained in:
jrandolf 2022-06-15 12:09:22 +02:00 committed by GitHub
parent 80373f7a12
commit 570087ea94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 4877 additions and 2954 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
/.dev_profile* /.dev_profile*
/.local-chromium/ /.local-chromium/
/.local-firefox/ /.local-firefox/
/test/test-user-data-dir* /test/output-*/
build/ build/
coverage coverage
coverage/ coverage/

View File

@ -86,9 +86,12 @@
"@microsoft/api-documenter": "7.17.16", "@microsoft/api-documenter": "7.17.16",
"@microsoft/api-extractor": "7.24.2", "@microsoft/api-extractor": "7.24.2",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/diff": "5.0.2",
"@types/mime": "2.0.3", "@types/mime": "2.0.3",
"@types/mocha": "9.1.1", "@types/mocha": "9.1.1",
"@types/node": "17.0.38", "@types/node": "17.0.38",
"@types/pixelmatch": "5.2.4",
"@types/pngjs": "6.0.1",
"@types/progress": "2.0.5", "@types/progress": "2.0.5",
"@types/proxy-from-env": "1.0.1", "@types/proxy-from-env": "1.0.1",
"@types/rimraf": "3.0.2", "@types/rimraf": "3.0.2",
@ -101,6 +104,7 @@
"c8": "7.11.3", "c8": "7.11.3",
"commonmark": "0.30.0", "commonmark": "0.30.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"diff": "5.1.0",
"eslint": "8.16.0", "eslint": "8.16.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-formatter-codeframe": "7.32.1", "eslint-formatter-codeframe": "7.32.1",

View File

@ -111,7 +111,9 @@ const waitFor = async (
return domWorld._waitForSelectorInPage( return domWorld._waitForSelectorInPage(
(_: Element, selector: string) => (_: Element, selector: string) =>
( (
globalThis as unknown as { ariaQuerySelector(selector: string): void } globalThis as any as unknown as {
ariaQuerySelector(selector: string): void;
}
).ariaQuerySelector(selector), ).ariaQuerySelector(selector),
selector, selector,
options, options,

View File

@ -719,10 +719,10 @@ export class DOMWorld {
function deliverResult(name: string, seq: number, result: unknown): void { function deliverResult(name: string, seq: number, result: unknown): void {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Code is evaluated in a different context. // @ts-ignore Code is evaluated in a different context.
globalThis[name].callbacks.get(seq).resolve(result); (globalThis as any)[name].callbacks.get(seq).resolve(result);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Code is evaluated in a different context. // @ts-ignore Code is evaluated in a different context.
globalThis[name].callbacks.delete(seq); (globalThis as any)[name].callbacks.delete(seq);
} }
}; };

View File

@ -65,7 +65,7 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
} }
return (...logArgs: unknown[]): void => { return (...logArgs: unknown[]): void => {
const debugLevel = globalThis.__PUPPETEER_DEBUG; const debugLevel = (globalThis as any).__PUPPETEER_DEBUG;
if (!debugLevel) { if (!debugLevel) {
return; return;
} }

View File

@ -1527,6 +1527,7 @@ const devices: Device[] = [
}, },
}, },
]; ];
/** /**
* @public * @public
*/ */

View File

@ -36,6 +36,7 @@ export class CustomError extends Error {
* @public * @public
*/ */
export class TimeoutError extends CustomError {} export class TimeoutError extends CustomError {}
/** /**
* ProtocolError is emitted whenever there is an error from the protocol. * ProtocolError is emitted whenever there is an error from the protocol.
* *
@ -45,13 +46,19 @@ export class ProtocolError extends CustomError {
public code?: number; public code?: number;
public originalMessage = ''; public originalMessage = '';
} }
/** /**
* @public * @public
*/ */
export type PuppeteerErrors = Record<string, typeof CustomError>; export interface PuppeteerErrors {
TimeoutError: typeof TimeoutError;
ProtocolError: typeof ProtocolError;
}
/** /**
* @public * @public
*/ */
export const puppeteerErrors: PuppeteerErrors = { export const puppeteerErrors: PuppeteerErrors = Object.freeze({
TimeoutError, TimeoutError,
}; ProtocolError,
});

View File

@ -2293,7 +2293,9 @@ export class Page extends EventEmitter {
*/ */
async emulateMediaType(type?: string): Promise<void> { async emulateMediaType(type?: string): Promise<void> {
assert( assert(
type === 'screen' || type === 'print' || type === null, type === 'screen' ||
type === 'print' ||
(type ?? undefined) === undefined,
'Unsupported media type: ' + type 'Unsupported media type: ' + type
); );
await this.#client.send('Emulation.setEmulatedMedia', { await this.#client.send('Emulation.setEmulatedMedia', {

View File

@ -16,5 +16,5 @@
/* Use the global version if we're in the browser, else load the node-fetch module. */ /* Use the global version if we're in the browser, else load the node-fetch module. */
export const getFetch = async (): Promise<typeof fetch> => { export const getFetch = async (): Promise<typeof fetch> => {
return globalThis.fetch || (await import('cross-fetch')).fetch; return (globalThis as any).fetch || (await import('cross-fetch')).fetch;
}; };

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
rules: { rules: {
'arrow-body-style': ['error', 'always'],
'no-restricted-imports': [ 'no-restricted-imports': [
'error', 'error',
{ {

View File

@ -2,7 +2,9 @@
const [, , puppeteerRoot, options] = process.argv; const [, , puppeteerRoot, options] = process.argv;
const browser = await require(puppeteerRoot).launch(JSON.parse(options)); const browser = await require(puppeteerRoot).launch(JSON.parse(options));
const page = await browser.newPage(); const page = await browser.newPage();
await page.evaluate(() => console.error('message from dumpio')); await page.evaluate(() => {
return console.error('message from dumpio');
});
await page.close(); await page.close();
await browser.close(); await browser.close();
})(); })();

View File

@ -21,7 +21,8 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeChromeOnly, describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { isErrorLike } from '../../lib/cjs/puppeteer/common/util.js';
describeChromeOnly('Target.createCDPSession', function () { describeChromeOnly('Target.createCDPSession', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -36,7 +37,9 @@ describeChromeOnly('Target.createCDPSession', function () {
client.send('Runtime.enable'), client.send('Runtime.enable'),
client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' }), client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' }),
]); ]);
const foo = await page.evaluate(() => globalThis.foo); const foo = await page.evaluate(() => {
return (globalThis as any).foo;
});
expect(foo).toBe('bar'); expect(foo).toBe('bar');
}); });
it('should send events', async () => { it('should send events', async () => {
@ -45,7 +48,9 @@ describeChromeOnly('Target.createCDPSession', function () {
const client = await page.target().createCDPSession(); const client = await page.target().createCDPSession();
await client.send('Network.enable'); await client.send('Network.enable');
const events = []; const events = [];
client.on('Network.requestWillBeSent', (event) => events.push(event)); client.on('Network.requestWillBeSent', (event) => {
return events.push(event);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(events.length).toBe(1); expect(events.length).toBe(1);
}); });
@ -77,14 +82,16 @@ describeChromeOnly('Target.createCDPSession', function () {
}); });
expect(evalResponse.result.value).toBe(3); expect(evalResponse.result.value).toBe(3);
await client.detach(); await client.detach();
let error = null; let error!: Error;
try { try {
await client.send('Runtime.evaluate', { await client.send('Runtime.evaluate', {
expression: '3 + 1', expression: '3 + 1',
returnByValue: true, returnByValue: true,
}); });
} catch (error_) { } catch (error_) {
error = error_; if (isErrorLike(error_)) {
error = error_ as Error;
}
} }
expect(error.message).toContain('Session closed.'); expect(error.message).toContain('Session closed.');
}); });
@ -92,7 +99,9 @@ describeChromeOnly('Target.createCDPSession', function () {
const { page } = getTestState(); const { page } = getTestState();
const client = await page.target().createCDPSession(); const client = await page.target().createCDPSession();
const error = await theSourceOfTheProblems().catch((error) => error); const error = await theSourceOfTheProblems().catch((error) => {
return error;
});
expect(error.stack).toContain('theSourceOfTheProblems'); expect(error.stack).toContain('theSourceOfTheProblems');
expect(error.message).toContain('ThisCommand.DoesNotExist'); expect(error.message).toContain('ThisCommand.DoesNotExist');

View File

@ -19,7 +19,7 @@ import sinon from 'sinon';
import expect from 'expect'; import expect from 'expect';
describe('EventEmitter', () => { describe('EventEmitter', () => {
let emitter; let emitter: EventEmitter;
beforeEach(() => { beforeEach(() => {
emitter = new EventEmitter(); emitter = new EventEmitter();
@ -40,7 +40,7 @@ describe('EventEmitter', () => {
emitter[methodName]('foo', listener); emitter[methodName]('foo', listener);
emitter.emit('foo', data); emitter.emit('foo', data);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
expect(listener.firstCall.args[0]).toBe(data); expect(listener.firstCall.args[0]!).toBe(data);
}); });
it(`${methodName}: supports chaining`, () => { it(`${methodName}: supports chaining`, () => {
@ -116,7 +116,7 @@ describe('EventEmitter', () => {
emitter.emit('foo', data); emitter.emit('foo', data);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
expect(listener.firstCall.args[0]).toBe(data); expect(listener.firstCall.args[0]!).toBe(data);
}); });
it('returns true if the event has listeners', () => { it('returns true if the event has listeners', () => {

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions import { describeChromeOnly } from './mocha-utils.js';
import expect from 'expect'; import expect from 'expect';
import { import {

View File

@ -14,13 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from 'assert';
import expect from 'expect'; import expect from 'expect';
import { SerializedAXNode } from '../../lib/cjs/puppeteer/common/Accessibility.js';
import { import {
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describeFailsFirefox('Accessibility', function () { describeFailsFirefox('Accessibility', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -170,7 +172,10 @@ describeFailsFirefox('Accessibility', function () {
); );
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
// See https://chromium-review.googlesource.com/c/chromium/src/+/3088862 // See https://chromium-review.googlesource.com/c/chromium/src/+/3088862
expect(snapshot.children[0].roledescription).toEqual(undefined); assert(snapshot);
assert(snapshot.children);
assert(snapshot.children[0]!);
expect(snapshot.children[0]!.roledescription).toBeUndefined();
}); });
it('orientation', async () => { it('orientation', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -179,14 +184,20 @@ describeFailsFirefox('Accessibility', function () {
'<a href="" role="slider" aria-orientation="vertical">11</a>' '<a href="" role="slider" aria-orientation="vertical">11</a>'
); );
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0].orientation).toEqual('vertical'); assert(snapshot);
assert(snapshot.children);
assert(snapshot.children[0]!);
expect(snapshot.children[0]!.orientation).toEqual('vertical');
}); });
it('autocomplete', async () => { it('autocomplete', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<input type="number" aria-autocomplete="list" />'); await page.setContent('<input type="number" aria-autocomplete="list" />');
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0].autocomplete).toEqual('list'); assert(snapshot);
assert(snapshot.children);
assert(snapshot.children[0]!);
expect(snapshot.children[0]!.autocomplete).toEqual('list');
}); });
it('multiselectable', async () => { it('multiselectable', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -195,7 +206,10 @@ describeFailsFirefox('Accessibility', function () {
'<div role="grid" tabIndex=-1 aria-multiselectable=true>hey</div>' '<div role="grid" tabIndex=-1 aria-multiselectable=true>hey</div>'
); );
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0].multiselectable).toEqual(true); assert(snapshot);
assert(snapshot.children);
assert(snapshot.children[0]!);
expect(snapshot.children[0]!.multiselectable).toEqual(true);
}); });
it('keyshortcuts', async () => { it('keyshortcuts', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -204,7 +218,10 @@ describeFailsFirefox('Accessibility', function () {
'<div role="grid" tabIndex=-1 aria-keyshortcuts="foo">hey</div>' '<div role="grid" tabIndex=-1 aria-keyshortcuts="foo">hey</div>'
); );
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0].keyshortcuts).toEqual('foo'); assert(snapshot);
assert(snapshot.children);
assert(snapshot.children[0]!);
expect(snapshot.children[0]!.keyshortcuts).toEqual('foo');
}); });
describe('filtering children of leaf nodes', function () { describe('filtering children of leaf nodes', function () {
it('should not report text nodes inside controls', async () => { it('should not report text nodes inside controls', async () => {
@ -286,7 +303,9 @@ describeFailsFirefox('Accessibility', function () {
], ],
}; };
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual(golden); assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual(golden);
}); });
it('rich text editable fields with role should have children', async () => { it('rich text editable fields with role should have children', async () => {
const { page, isFirefox } = getTestState(); const { page, isFirefox } = getTestState();
@ -324,7 +343,9 @@ describeFailsFirefox('Accessibility', function () {
], ],
}; };
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual(golden); assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual(golden);
}); });
// Firefox does not support contenteditable="plaintext-only". // Firefox does not support contenteditable="plaintext-only".
@ -335,7 +356,9 @@ describeFailsFirefox('Accessibility', function () {
await page.setContent(` await page.setContent(`
<div contenteditable="plaintext-only" role='textbox'>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`); <div contenteditable="plaintext-only" role='textbox'>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual({ assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual({
role: 'textbox', role: 'textbox',
name: '', name: '',
value: 'Edit this image:', value: 'Edit this image:',
@ -363,7 +386,9 @@ describeFailsFirefox('Accessibility', function () {
value: 'this is the inner content ', value: 'this is the inner content ',
}; };
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual(golden); assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual(golden);
}); });
it('checkbox with and tabIndex and label should not have children', async () => { it('checkbox with and tabIndex and label should not have children', async () => {
const { page, isFirefox } = getTestState(); const { page, isFirefox } = getTestState();
@ -385,7 +410,9 @@ describeFailsFirefox('Accessibility', function () {
checked: true, checked: true,
}; };
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual(golden); assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual(golden);
}); });
it('checkbox without label should not have children', async () => { it('checkbox without label should not have children', async () => {
const { page, isFirefox } = getTestState(); const { page, isFirefox } = getTestState();
@ -407,7 +434,9 @@ describeFailsFirefox('Accessibility', function () {
checked: true, checked: true,
}; };
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0]).toEqual(golden); assert(snapshot);
assert(snapshot.children);
expect(snapshot.children[0]!).toEqual(golden);
}); });
describe('root option', function () { describe('root option', function () {
@ -416,7 +445,7 @@ describeFailsFirefox('Accessibility', function () {
await page.setContent(`<button>My Button</button>`); await page.setContent(`<button>My Button</button>`);
const button = await page.$<HTMLButtonElement>('button'); const button = (await page.$('button'))!;
expect(await page.accessibility.snapshot({ root: button })).toEqual({ expect(await page.accessibility.snapshot({ root: button })).toEqual({
role: 'button', role: 'button',
name: 'My Button', name: 'My Button',
@ -427,7 +456,7 @@ describeFailsFirefox('Accessibility', function () {
await page.setContent(`<input title="My Input" value="My Value">`); await page.setContent(`<input title="My Input" value="My Value">`);
const input = await page.$('input'); const input = (await page.$('input'))!;
expect(await page.accessibility.snapshot({ root: input })).toEqual({ expect(await page.accessibility.snapshot({ root: input })).toEqual({
role: 'textbox', role: 'textbox',
name: 'My Input', name: 'My Input',
@ -445,7 +474,7 @@ describeFailsFirefox('Accessibility', function () {
</div> </div>
`); `);
const menu = await page.$('div[role="menu"]'); const menu = (await page.$('div[role="menu"]'))!;
expect(await page.accessibility.snapshot({ root: menu })).toEqual({ expect(await page.accessibility.snapshot({ root: menu })).toEqual({
role: 'menu', role: 'menu',
name: 'My Menu', name: 'My Menu',
@ -461,8 +490,10 @@ describeFailsFirefox('Accessibility', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<button>My Button</button>`); await page.setContent(`<button>My Button</button>`);
const button = await page.$('button'); const button = (await page.$('button'))!;
await page.$eval('button', (button) => button.remove()); await page.$eval('button', (button) => {
return button.remove();
});
expect(await page.accessibility.snapshot({ root: button })).toEqual( expect(await page.accessibility.snapshot({ root: button })).toEqual(
null null
); );
@ -471,7 +502,7 @@ describeFailsFirefox('Accessibility', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<div><button>My Button</button></div>`); await page.setContent(`<div><button>My Button</button></div>`);
const div = await page.$('div'); const div = (await page.$('div'))!;
expect(await page.accessibility.snapshot({ root: div })).toEqual(null); expect(await page.accessibility.snapshot({ root: div })).toEqual(null);
expect( expect(
await page.accessibility.snapshot({ await page.accessibility.snapshot({
@ -492,11 +523,14 @@ describeFailsFirefox('Accessibility', function () {
}); });
}); });
}); });
function findFocusedNode(node) {
if (node.focused) { function findFocusedNode(
node: SerializedAXNode | null
): SerializedAXNode | null {
if (node?.focused) {
return node; return node;
} }
for (const child of node.children || []) { for (const child of node?.children || []) {
const focusedChild = findFocusedNode(child); const focusedChild = findFocusedNode(child);
if (focusedChild) { if (focusedChild) {
return focusedChild; return focusedChild;

View File

@ -20,10 +20,11 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeChromeOnly, describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { ElementHandle } from '../../lib/cjs/puppeteer/common/JSHandle.js'; import { ElementHandle } from '../../lib/cjs/puppeteer/common/JSHandle.js';
import utils from './utils.js'; import utils from './utils.js';
import assert from 'assert';
describeChromeOnly('AriaQueryHandler', () => { describeChromeOnly('AriaQueryHandler', () => {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -38,8 +39,11 @@ describeChromeOnly('AriaQueryHandler', () => {
}); });
it('should find button', async () => { it('should find button', async () => {
const { page } = getTestState(); const { page } = getTestState();
const expectFound = async (button: ElementHandle) => { const expectFound = async (button: ElementHandle | null) => {
const id = await button.evaluate((button: Element) => button.id); assert(button);
const id = await button.evaluate((button: Element) => {
return button.id;
});
expect(id).toBe('btn'); expect(id).toBe('btn');
}; };
let button = await page.$( let button = await page.$(
@ -94,8 +98,10 @@ describeChromeOnly('AriaQueryHandler', () => {
await page.setContent( await page.setContent(
'<div id="div"><button id="btn" role="button">Submit</button></div>' '<div id="div"><button id="btn" role="button">Submit</button></div>'
); );
const button = await page.$('aria/[role="button"]'); const button = (await page.$('aria/[role="button"]'))!;
const id = await button.evaluate((button: Element) => button.id); const id = await button!.evaluate((button: Element) => {
return button.id;
});
expect(id).toBe('btn'); expect(id).toBe('btn');
}); });
@ -104,8 +110,10 @@ describeChromeOnly('AriaQueryHandler', () => {
await page.setContent( await page.setContent(
'<div id="div"><button id="btn" role="button">Submit</button></div>' '<div id="div"><button id="btn" role="button">Submit</button></div>'
); );
const button = await page.$('aria/Submit[role="button"]'); const button = (await page.$('aria/Submit[role="button"]'))!;
const id = await button.evaluate((button: Element) => button.id); const id = await button!.evaluate((button: Element) => {
return button.id;
});
expect(id).toBe('btn'); expect(id).toBe('btn');
}); });
@ -117,8 +125,10 @@ describeChromeOnly('AriaQueryHandler', () => {
<div role="menu" id="mnu2" aria-label="menu div"></div> <div role="menu" id="mnu2" aria-label="menu div"></div>
` `
); );
const div = await page.$('aria/menu div'); const div = (await page.$('aria/menu div'))!;
const id = await div.evaluate((div: Element) => div.id); const id = await div!.evaluate((div: Element) => {
return div.id;
});
expect(id).toBe('mnu1'); expect(id).toBe('mnu1');
}); });
@ -130,8 +140,10 @@ describeChromeOnly('AriaQueryHandler', () => {
<div role="menu" id="mnu2" aria-label="menu-label2">menu div</div> <div role="menu" id="mnu2" aria-label="menu-label2">menu div</div>
` `
); );
const menu = await page.$('aria/menu-label1'); const menu = (await page.$('aria/menu-label1'))!;
const id = await menu.evaluate((div: Element) => div.id); const id = await menu!.evaluate((div: Element) => {
return div.id;
});
expect(id).toBe('mnu1'); expect(id).toBe('mnu1');
}); });
@ -143,8 +155,10 @@ describeChromeOnly('AriaQueryHandler', () => {
<div role="menu" id="mnu2" aria-label="menu-label2">menu div</div> <div role="menu" id="mnu2" aria-label="menu-label2">menu div</div>
` `
); );
const menu = await page.$('aria/menu-label2'); const menu = (await page.$('aria/menu-label2'))!;
const id = await menu.evaluate((div: Element) => div.id); const id = await menu!.evaluate((div: Element) => {
return div.id;
});
expect(id).toBe('mnu2'); expect(id).toBe('mnu2');
}); });
}); });
@ -160,7 +174,11 @@ describeChromeOnly('AriaQueryHandler', () => {
); );
const divs = await page.$$('aria/menu div'); const divs = await page.$$('aria/menu div');
const ids = await Promise.all( const ids = await Promise.all(
divs.map((n) => n.evaluate((div: Element) => div.id)) divs.map((n) => {
return n.evaluate((div: Element) => {
return div.id;
});
})
); );
expect(ids.join(', ')).toBe('mnu1, mnu2'); expect(ids.join(', ')).toBe('mnu1, mnu2');
}); });
@ -178,16 +196,19 @@ describeChromeOnly('AriaQueryHandler', () => {
} }
` `
); );
const sum = await page.$$eval('aria/[role="button"]', (buttons) => const sum = await page.$$eval('aria/[role="button"]', (buttons) => {
buttons.reduce((acc, button) => acc + Number(button.textContent), 0) return buttons.reduce((acc, button) => {
); return acc + Number(button.textContent);
}, 0);
});
expect(sum).toBe(50005000); expect(sum).toBe(50005000);
}); });
}); });
describe('waitForSelector (aria)', function () { describe('waitForSelector (aria)', function () {
const addElement = (tag) => const addElement = (tag: string) => {
document.body.appendChild(document.createElement(tag)); return document.body.appendChild(document.createElement(tag));
};
it('should immediately resolve promise if node exists', async () => { it('should immediately resolve promise if node exists', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -199,11 +220,11 @@ describeChromeOnly('AriaQueryHandler', () => {
it('should work for ElementHandler.waitForSelector', async () => { it('should work for ElementHandler.waitForSelector', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate( await page.evaluate(() => {
() => (document.body.innerHTML = `<div><button>test</button></div>`) return (document.body.innerHTML = `<div><button>test</button></div>`);
); });
const element = await page.$('div'); const element = (await page.$('div'))!;
await element.waitForSelector('aria/test'); await element!.waitForSelector('aria/test');
}); });
it('should persist query handler bindings across reloads', async () => { it('should persist query handler bindings across reloads', async () => {
@ -235,7 +256,9 @@ describeChromeOnly('AriaQueryHandler', () => {
it('should work independently of `exposeFunction`', async () => { it('should work independently of `exposeFunction`', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.exposeFunction('ariaQuerySelector', (a, b) => a + b); await page.exposeFunction('ariaQuerySelector', (a: number, b: number) => {
return a + b;
});
await page.evaluate(addElement, 'button'); await page.evaluate(addElement, 'button');
await page.waitForSelector('aria/[role="button"]'); await page.waitForSelector('aria/[role="button"]');
const result = await page.evaluate('globalThis.ariaQuerySelector(2,8)'); const result = await page.evaluate('globalThis.ariaQuerySelector(2,8)');
@ -245,13 +268,18 @@ describeChromeOnly('AriaQueryHandler', () => {
it('should work with removed MutationObserver', async () => { it('should work with removed MutationObserver', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => {
// @ts-expect-error This is the point of the test.
return delete window.MutationObserver;
});
const [handle] = await Promise.all([ const [handle] = await Promise.all([
page.waitForSelector('aria/anything'), page.waitForSelector('aria/anything'),
page.setContent(`<h1>anything</h1>`), page.setContent(`<h1>anything</h1>`),
]); ]);
expect( expect(
await page.evaluate((x: HTMLElement) => x.textContent, handle) await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, handle)
).toBe('anything'); ).toBe('anything');
}); });
@ -263,10 +291,10 @@ describeChromeOnly('AriaQueryHandler', () => {
const watchdog = frame.waitForSelector('aria/[role="heading"]'); const watchdog = frame.waitForSelector('aria/[role="heading"]');
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'h1'); await frame.evaluate(addElement, 'h1');
const elementHandle = await watchdog; const elementHandle = (await watchdog)!;
const tagName = await elementHandle const tagName = await (
.getProperty('tagName') await elementHandle.getProperty('tagName')
.then((element) => element.jsonValue()); ).jsonValue();
expect(tagName).toBe('H1'); expect(tagName).toBe('H1');
}); });
@ -276,11 +304,10 @@ describeChromeOnly('AriaQueryHandler', () => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('aria/name'); const watchdog = page.waitForSelector('aria/name');
await page.evaluate(addElement, 'span'); await page.evaluate(addElement, 'span');
await page.evaluate( await page.evaluate(() => {
() => return (document.querySelector('span')!.innerHTML =
(document.querySelector('span').innerHTML = '<h3><div aria-label="name"></div></h3>');
'<h3><div aria-label="name"></div></h3>') });
);
await watchdog; await watchdog;
}); });
@ -291,10 +318,10 @@ describeChromeOnly('AriaQueryHandler', () => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1]; const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('aria/[role="button"]'); const watchdog = page.waitForSelector('aria/[role="button"]');
await otherFrame.evaluate(addElement, 'button'); await otherFrame!.evaluate(addElement, 'button');
await page.evaluate(addElement, 'button'); await page.evaluate(addElement, 'button');
const elementHandle = await watchdog; const elementHandle = await watchdog;
expect(elementHandle.executionContext().frame()).toBe(page.mainFrame()); expect(elementHandle!.executionContext().frame()).toBe(page.mainFrame());
}); });
it('should run in specified frame', async () => { it('should run in specified frame', async () => {
@ -304,13 +331,13 @@ describeChromeOnly('AriaQueryHandler', () => {
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
const frame2 = page.frames()[2]; const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector( const waitForSelectorPromise = frame2!.waitForSelector(
'aria/[role="button"]' 'aria/[role="button"]'
); );
await frame1.evaluate(addElement, 'button'); await frame1!.evaluate(addElement, 'button');
await frame2.evaluate(addElement, 'button'); await frame2!.evaluate(addElement, 'button');
const elementHandle = await waitForSelectorPromise; const elementHandle = await waitForSelectorPromise;
expect(elementHandle.executionContext().frame()).toBe(frame2); expect(elementHandle!.executionContext().frame()).toBe(frame2);
}); });
it('should throw when frame is detached', async () => { it('should throw when frame is detached', async () => {
@ -318,10 +345,12 @@ describeChromeOnly('AriaQueryHandler', () => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1];
let waitError = null; let waitError!: Error;
const waitPromise = frame const waitPromise = frame!
.waitForSelector('aria/does-not-exist') .waitForSelector('aria/does-not-exist')
.catch((error) => (waitError = error)); .catch((error) => {
return (waitError = error);
});
await utils.detachFrame(page, 'frame1'); await utils.detachFrame(page, 'frame1');
await waitPromise; await waitPromise;
expect(waitError).toBeTruthy(); expect(waitError).toBeTruthy();
@ -336,7 +365,9 @@ describeChromeOnly('AriaQueryHandler', () => {
let imgFound = false; let imgFound = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/[role="img"]') .waitForSelector('aria/[role="img"]')
.then(() => (imgFound = true)); .then(() => {
return (imgFound = true);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(imgFound).toBe(false); expect(imgFound).toBe(false);
await page.reload(); await page.reload();
@ -352,18 +383,22 @@ describeChromeOnly('AriaQueryHandler', () => {
let divFound = false; let divFound = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/name', { visible: true }) .waitForSelector('aria/name', { visible: true })
.then(() => (divFound = true)); .then(() => {
return (divFound = true);
});
await page.setContent( await page.setContent(
`<div aria-label='name' style='display: none; visibility: hidden;'>1</div>` `<div aria-label='name' style='display: none; visibility: hidden;'>1</div>`
); );
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('display') return document.querySelector('div')!.style.removeProperty('display');
); });
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('visibility') return document
); .querySelector('div')!
.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divFound).toBe(true); expect(divFound).toBe(true);
}); });
@ -374,18 +409,22 @@ describeChromeOnly('AriaQueryHandler', () => {
let divVisible = false; let divVisible = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/inner', { visible: true }) .waitForSelector('aria/inner', { visible: true })
.then(() => (divVisible = true)); .then(() => {
return (divVisible = true);
});
await page.setContent( await page.setContent(
`<div style='display: none; visibility: hidden;'><div aria-label="inner">hi</div></div>` `<div style='display: none; visibility: hidden;'><div aria-label="inner">hi</div></div>`
); );
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('display') return document.querySelector('div')!.style.removeProperty('display');
); });
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('visibility') return document
); .querySelector('div')!
.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true); expect(divVisible).toBe(true);
}); });
@ -399,12 +438,16 @@ describeChromeOnly('AriaQueryHandler', () => {
); );
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/[role="button"]', { hidden: true }) .waitForSelector('aria/[role="button"]', { hidden: true })
.then(() => (divHidden = true)); .then(() => {
return (divHidden = true);
});
await page.waitForSelector('aria/[role="button"]'); // do a round trip await page.waitForSelector('aria/[role="button"]'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.setProperty('visibility', 'hidden') return document
); .querySelector('div')!
.style.setProperty('visibility', 'hidden');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
@ -416,12 +459,16 @@ describeChromeOnly('AriaQueryHandler', () => {
await page.setContent(`<div role='main' style='display: block;'></div>`); await page.setContent(`<div role='main' style='display: block;'></div>`);
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/[role="main"]', { hidden: true }) .waitForSelector('aria/[role="main"]', { hidden: true })
.then(() => (divHidden = true)); .then(() => {
return (divHidden = true);
});
await page.waitForSelector('aria/[role="main"]'); // do a round trip await page.waitForSelector('aria/[role="main"]'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.setProperty('display', 'none') return document
); .querySelector('div')!
.style.setProperty('display', 'none');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
@ -433,10 +480,14 @@ describeChromeOnly('AriaQueryHandler', () => {
let divRemoved = false; let divRemoved = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('aria/[role="main"]', { hidden: true }) .waitForSelector('aria/[role="main"]', { hidden: true })
.then(() => (divRemoved = true)); .then(() => {
return (divRemoved = true);
});
await page.waitForSelector('aria/[role="main"]'); // do a round trip await page.waitForSelector('aria/[role="main"]'); // do a round trip
expect(divRemoved).toBe(false); expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove()); await page.evaluate(() => {
return document.querySelector('div')!.remove();
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divRemoved).toBe(true); expect(divRemoved).toBe(true);
}); });
@ -453,10 +504,12 @@ describeChromeOnly('AriaQueryHandler', () => {
it('should respect timeout', async () => { it('should respect timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
let error = null; let error!: Error;
await page await page
.waitForSelector('aria/[role="button"]', { timeout: 10 }) .waitForSelector('aria/[role="button"]', { timeout: 10 })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain( expect(error.message).toContain(
'waiting for selector `[role="button"]` failed: timeout' 'waiting for selector `[role="button"]` failed: timeout'
@ -468,10 +521,12 @@ describeChromeOnly('AriaQueryHandler', () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<div role='main'></div>`); await page.setContent(`<div role='main'></div>`);
let error = null; let error!: Error;
await page await page
.waitForSelector('aria/[role="main"]', { hidden: true, timeout: 10 }) .waitForSelector('aria/[role="main"]', { hidden: true, timeout: 10 })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain( expect(error.message).toContain(
'waiting for selector `[role="main"]` to be hidden failed: timeout' 'waiting for selector `[role="main"]` to be hidden failed: timeout'
@ -482,14 +537,16 @@ describeChromeOnly('AriaQueryHandler', () => {
const { page } = getTestState(); const { page } = getTestState();
let divFound = false; let divFound = false;
const waitForSelector = page const waitForSelector = page.waitForSelector('aria/zombo').then(() => {
.waitForSelector('aria/zombo') return (divFound = true);
.then(() => (divFound = true)); });
await page.setContent(`<div aria-label='notZombo'></div>`); await page.setContent(`<div aria-label='notZombo'></div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').setAttribute('aria-label', 'zombo') return document
); .querySelector('div')!
.setAttribute('aria-label', 'zombo');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
}); });
@ -499,21 +556,22 @@ describeChromeOnly('AriaQueryHandler', () => {
const waitForSelector = page.waitForSelector('aria/zombo'); const waitForSelector = page.waitForSelector('aria/zombo');
await page.setContent(`<div aria-label='zombo'>anything</div>`); await page.setContent(`<div aria-label='zombo'>anything</div>`);
expect( expect(
await page.evaluate( await page.evaluate((x: HTMLElement) => {
(x: HTMLElement) => x.textContent, return x.textContent;
await waitForSelector }, await waitForSelector)
)
).toBe('anything'); ).toBe('anything');
}); });
it('should have correct stack trace for timeout', async () => { it('should have correct stack trace for timeout', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error; let error!: Error;
await page await page
.waitForSelector('aria/zombo', { timeout: 10 }) .waitForSelector('aria/zombo', { timeout: 10 })
.catch((error_) => (error = error_)); .catch((error_) => {
expect(error.stack).toContain('waiting for selector `zombo` failed'); return (error = error_);
});
expect(error!.stack).toContain('waiting for selector `zombo` failed');
}); });
}); });
@ -566,12 +624,15 @@ describeChromeOnly('AriaQueryHandler', () => {
` `
); );
}); });
const getIds = async (elements: ElementHandle[]) => const getIds = async (elements: ElementHandle[]) => {
Promise.all( return Promise.all(
elements.map((element) => elements.map((element) => {
element.evaluate((element: Element) => element.id) return element.evaluate((element: Element) => {
) return element.id;
});
})
); );
};
it('should find by name "foo"', async () => { it('should find by name "foo"', async () => {
const { page } = getTestState(); const { page } = getTestState();
const found = await page.$$('aria/foo'); const found = await page.$$('aria/foo');

View File

@ -15,7 +15,7 @@
*/ */
import expect from 'expect'; import expect from 'expect';
import { getTestState, setupTestBrowserHooks } from './mocha-utils'; // eslint-disable-line import/extensions import { getTestState, setupTestBrowserHooks } from './mocha-utils.js';
describe('Browser specs', function () { describe('Browser specs', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -60,7 +60,7 @@ describe('Browser specs', function () {
const { browser } = getTestState(); const { browser } = getTestState();
const process = await browser.process(); const process = await browser.process();
expect(process.pid).toBeGreaterThan(0); expect(process!.pid).toBeGreaterThan(0);
}); });
it('should not return child_process for remote browser', async () => { it('should not return child_process for remote browser', async () => {
const { browser, puppeteer } = getTestState(); const { browser, puppeteer } = getTestState();

View File

@ -19,7 +19,7 @@ import {
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import utils from './utils.js'; import utils from './utils.js';
describe('BrowserContext', function () { describe('BrowserContext', function () {
@ -27,10 +27,12 @@ describe('BrowserContext', function () {
it('should have default context', async () => { it('should have default context', async () => {
const { browser } = getTestState(); const { browser } = getTestState();
expect(browser.browserContexts().length).toEqual(1); expect(browser.browserContexts().length).toEqual(1);
const defaultContext = browser.browserContexts()[0]; const defaultContext = browser.browserContexts()[0]!;
expect(defaultContext.isIncognito()).toBe(false); expect(defaultContext!.isIncognito()).toBe(false);
let error = null; let error!: Error;
await defaultContext.close().catch((error_) => (error = error_)); await defaultContext!.close().catch((error_) => {
return (error = error_);
});
expect(browser.defaultBrowserContext()).toBe(defaultContext); expect(browser.defaultBrowserContext()).toBe(defaultContext);
expect(error.message).toContain('cannot be closed'); expect(error.message).toContain('cannot be closed');
}); });
@ -66,10 +68,9 @@ describe('BrowserContext', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [popupTarget] = await Promise.all([ const [popupTarget] = await Promise.all([
utils.waitEvent(browser, 'targetcreated'), utils.waitEvent(browser, 'targetcreated'),
page.evaluate<(url: string) => void>( page.evaluate<(url: string) => void>((url) => {
(url) => window.open(url), return window.open(url);
server.EMPTY_PAGE }, server.EMPTY_PAGE),
),
]); ]);
expect(popupTarget.browserContext()).toBe(context); expect(popupTarget.browserContext()).toBe(context);
await context.close(); await context.close();
@ -78,16 +79,16 @@ describe('BrowserContext', function () {
const { browser, server } = getTestState(); const { browser, server } = getTestState();
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
const events = []; const events: any[] = [];
context.on('targetcreated', (target) => context.on('targetcreated', (target) => {
events.push('CREATED: ' + target.url()) return events.push('CREATED: ' + target.url());
); });
context.on('targetchanged', (target) => context.on('targetchanged', (target) => {
events.push('CHANGED: ' + target.url()) return events.push('CHANGED: ' + target.url());
); });
context.on('targetdestroyed', (target) => context.on('targetdestroyed', (target) => {
events.push('DESTROYED: ' + target.url()) return events.push('DESTROYED: ' + target.url());
); });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.close(); await page.close();
@ -104,11 +105,13 @@ describe('BrowserContext', function () {
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
let resolved = false; let resolved = false;
const targetPromise = context.waitForTarget( const targetPromise = context.waitForTarget((target) => {
(target) => target.url() === server.EMPTY_PAGE return target.url() === server.EMPTY_PAGE;
); });
targetPromise targetPromise
.then(() => (resolved = true)) .then(() => {
return (resolved = true);
})
.catch((error) => { .catch((error) => {
resolved = true; resolved = true;
if (error instanceof puppeteer.errors.TimeoutError) { if (error instanceof puppeteer.errors.TimeoutError) {
@ -138,10 +141,17 @@ describe('BrowserContext', function () {
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
const error = await context const error = await context
.waitForTarget((target) => target.url() === server.EMPTY_PAGE, { .waitForTarget(
(target) => {
return target.url() === server.EMPTY_PAGE;
},
{
timeout: 1, timeout: 1,
}) }
.catch((error_) => error_); )
.catch((error_) => {
return error_;
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
await context.close(); await context.close();
}); });
@ -175,19 +185,31 @@ describe('BrowserContext', function () {
}); });
expect(context1.targets().length).toBe(1); expect(context1.targets().length).toBe(1);
expect(context1.targets()[0]).toBe(page1.target()); expect(context1.targets()[0]!).toBe(page1.target());
expect(context2.targets().length).toBe(1); expect(context2.targets().length).toBe(1);
expect(context2.targets()[0]).toBe(page2.target()); expect(context2.targets()[0]!).toBe(page2.target());
// Make sure pages don't share localstorage or cookies. // Make sure pages don't share localstorage or cookies.
expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe( expect(
'page1' await page1.evaluate(() => {
); return localStorage.getItem('name');
expect(await page1.evaluate(() => document.cookie)).toBe('name=page1'); })
expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe( ).toBe('page1');
'page2' expect(
); await page1.evaluate(() => {
expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); return document.cookie;
})
).toBe('name=page1');
expect(
await page2.evaluate(() => {
return localStorage.getItem('name');
})
).toBe('page2');
expect(
await page2.evaluate(() => {
return document.cookie;
})
).toBe('name=page2');
// Cleanup contexts. // Cleanup contexts.
await Promise.all([context1.close(), context2.close()]); await Promise.all([context1.close(), context2.close()]);

View File

@ -14,12 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import expect from 'expect'; import expect from 'expect';
import { IncomingMessage } from 'http';
import { import {
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeChromeOnly, describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describeChromeOnly('Chromium-Specific Launcher tests', function () { describeChromeOnly('Chromium-Specific Launcher tests', function () {
describe('Puppeteer.launch |browserURL| option', function () { describe('Puppeteer.launch |browserURL| option', function () {
@ -35,14 +36,22 @@ describeChromeOnly('Chromium-Specific Launcher tests', function () {
const browser1 = await puppeteer.connect({ browserURL }); const browser1 = await puppeteer.connect({ browserURL });
const page1 = await browser1.newPage(); const page1 = await browser1.newPage();
expect(await page1.evaluate(() => 7 * 8)).toBe(56); expect(
await page1.evaluate(() => {
return 7 * 8;
})
).toBe(56);
browser1.disconnect(); browser1.disconnect();
const browser2 = await puppeteer.connect({ const browser2 = await puppeteer.connect({
browserURL: browserURL + '/', browserURL: browserURL + '/',
}); });
const page2 = await browser2.newPage(); const page2 = await browser2.newPage();
expect(await page2.evaluate(() => 8 * 7)).toBe(56); expect(
await page2.evaluate(() => {
return 8 * 7;
})
).toBe(56);
browser2.disconnect(); browser2.disconnect();
originalBrowser.close(); originalBrowser.close();
}); });
@ -56,13 +65,15 @@ describeChromeOnly('Chromium-Specific Launcher tests', function () {
); );
const browserURL = 'http://127.0.0.1:21222'; const browserURL = 'http://127.0.0.1:21222';
let error = null; let error!: Error;
await puppeteer await puppeteer
.connect({ .connect({
browserURL, browserURL,
browserWSEndpoint: originalBrowser.wsEndpoint(), browserWSEndpoint: originalBrowser.wsEndpoint(),
}) })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toContain( expect(error.message).toContain(
'Exactly one of browserWSEndpoint, browserURL or transport' 'Exactly one of browserWSEndpoint, browserURL or transport'
); );
@ -79,10 +90,10 @@ describeChromeOnly('Chromium-Specific Launcher tests', function () {
); );
const browserURL = 'http://127.0.0.1:32333'; const browserURL = 'http://127.0.0.1:32333';
let error = null; let error!: Error;
await puppeteer await puppeteer.connect({ browserURL }).catch((error_) => {
.connect({ browserURL }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.message).toContain( expect(error.message).toContain(
'Failed to fetch browser webSocket URL from' 'Failed to fetch browser webSocket URL from'
); );
@ -117,11 +128,11 @@ describeChromeOnly('Chromium-Specific Launcher tests', function () {
const { defaultBrowserOptions, puppeteer } = getTestState(); const { defaultBrowserOptions, puppeteer } = getTestState();
const options = Object.assign({ pipe: true }, defaultBrowserOptions); const options = Object.assign({ pipe: true }, defaultBrowserOptions);
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
const disconnectedEventPromise = new Promise((resolve) => const disconnectedEventPromise = new Promise((resolve) => {
browser.once('disconnected', resolve) return browser.once('disconnected', resolve);
); });
// Emulate user exiting browser. // Emulate user exiting browser.
browser.process().kill(); browser.process()!.kill();
await disconnectedEventPromise; await disconnectedEventPromise;
}); });
}); });
@ -133,26 +144,28 @@ describeChromeOnly('Chromium-Specific Page Tests', function () {
it('Page.setRequestInterception should work with intervention headers', async () => { it('Page.setRequestInterception should work with intervention headers', async () => {
const { server, page } = getTestState(); const { server, page } = getTestState();
server.setRoute('/intervention', (req, res) => server.setRoute('/intervention', (_req, res) => {
res.end(` return res.end(`
<script> <script>
document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>'); document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>');
</script> </script>
`) `);
); });
server.setRedirect('/intervention.js', '/redirect.js'); server.setRedirect('/intervention.js', '/redirect.js');
let serverRequest = null; let serverRequest: IncomingMessage | undefined;
server.setRoute('/redirect.js', (req, res) => { server.setRoute('/redirect.js', (req, res) => {
serverRequest = req; serverRequest = req;
res.end('console.log(1);'); res.end('console.log(1);');
}); });
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
await page.goto(server.PREFIX + '/intervention'); await page.goto(server.PREFIX + '/intervention');
// Check for feature URL substring rather than https://www.chromestatus.com to // Check for feature URL substring rather than https://www.chromestatus.com to
// make it work with Edgium. // make it work with Edgium.
expect(serverRequest.headers.intervention).toContain( expect(serverRequest!.headers['intervention']).toContain(
'feature/5718547946799104' 'feature/5718547946799104'
); );
}); });

View File

@ -20,7 +20,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
setupTestBrowserHooks, setupTestBrowserHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import utils from './utils.js'; import utils from './utils.js';
describe('Page.click', function () { describe('Page.click', function () {
@ -31,7 +31,11 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should click svg', async () => { it('should click svg', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -42,7 +46,11 @@ describe('Page.click', function () {
</svg> </svg>
`); `);
await page.click('circle'); await page.click('circle');
expect(await page.evaluate(() => globalThis.__CLICKED)).toBe(42); expect(
await page.evaluate(() => {
return (globalThis as any).__CLICKED;
})
).toBe(42);
}); });
itFailsFirefox( itFailsFirefox(
'should click the button if window.Node is removed', 'should click the button if window.Node is removed',
@ -50,9 +58,16 @@ describe('Page.click', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => delete window.Node); await page.evaluate(() => {
// @ts-expect-error Expected.
return delete window.Node;
});
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
} }
); );
// @see https://github.com/puppeteer/puppeteer/issues/4281 // @see https://github.com/puppeteer/puppeteer/issues/4281
@ -68,7 +83,11 @@ describe('Page.click', function () {
<span onclick='javascript:window.CLICKED=42'></span> <span onclick='javascript:window.CLICKED=42'></span>
`); `);
await page.click('span'); await page.click('span');
expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42); expect(
await page.evaluate(() => {
return (globalThis as any).CLICKED;
})
).toBe(42);
}); });
it('should not throw UnhandledPromiseRejection when page closes', async () => { it('should not throw UnhandledPromiseRejection when page closes', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -85,7 +104,11 @@ describe('Page.click', function () {
await page.click('button'); await page.click('button');
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
itFailsFirefox('should click with disabled javascript', async () => { itFailsFirefox('should click with disabled javascript', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -108,7 +131,11 @@ describe('Page.click', function () {
<span onclick='javascript:window.CLICKED = 42;'><i>woof</i><b>doggo</b></span> <span onclick='javascript:window.CLICKED = 42;'><i>woof</i><b>doggo</b></span>
`); `);
await page.click('span'); await page.click('span');
expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42); expect(
await page.evaluate(() => {
return (globalThis as any).CLICKED;
})
).toBe(42);
}); });
it('should select the text by triple clicking', async () => { it('should select the text by triple clicking', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -124,9 +151,9 @@ describe('Page.click', function () {
expect( expect(
await page.evaluate(() => { await page.evaluate(() => {
const textarea = document.querySelector('textarea'); const textarea = document.querySelector('textarea');
return textarea.value.substring( return textarea!.value.substring(
textarea.selectionStart, textarea!.selectionStart,
textarea.selectionEnd textarea!.selectionEnd
); );
}) })
).toBe(text); ).toBe(text);
@ -135,11 +162,15 @@ describe('Page.click', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
const messages = []; const messages: any[] = [];
page.on('console', (msg) => messages.push(msg.text())); page.on('console', (msg) => {
return messages.push(msg.text());
});
for (let i = 0; i < 11; ++i) { for (let i = 0; i < 11; ++i) {
// We might've scrolled to click a button - reset to (0, 0). // We might've scrolled to click a button - reset to (0, 0).
await page.evaluate(() => window.scrollTo(0, 0)); await page.evaluate(() => {
return window.scrollTo(0, 0);
});
await page.click(`#btn${i}`); await page.click(`#btn${i}`);
} }
expect(messages).toEqual([ expect(messages).toEqual([
@ -162,17 +193,33 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/wrappedlink.html'); await page.goto(server.PREFIX + '/wrappedlink.html');
await page.click('a'); await page.click('a');
expect(await page.evaluate(() => globalThis.__clicked)).toBe(true); expect(
await page.evaluate(() => {
return (globalThis as any).__clicked;
})
).toBe(true);
}); });
it('should click on checkbox input and toggle', async () => { it('should click on checkbox input and toggle', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/checkbox.html'); await page.goto(server.PREFIX + '/input/checkbox.html');
expect(await page.evaluate(() => globalThis.result.check)).toBe(null); expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(null);
await page.click('input#agree'); await page.click('input#agree');
expect(await page.evaluate(() => globalThis.result.check)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.result.events)).toEqual([ await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).result.events;
})
).toEqual([
'mouseover', 'mouseover',
'mouseenter', 'mouseenter',
'mousemove', 'mousemove',
@ -183,33 +230,49 @@ describe('Page.click', function () {
'change', 'change',
]); ]);
await page.click('input#agree'); await page.click('input#agree');
expect(await page.evaluate(() => globalThis.result.check)).toBe(false); expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(false);
}); });
itFailsFirefox('should click on checkbox label and toggle', async () => { itFailsFirefox('should click on checkbox label and toggle', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/checkbox.html'); await page.goto(server.PREFIX + '/input/checkbox.html');
expect(await page.evaluate(() => globalThis.result.check)).toBe(null); expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(null);
await page.click('label[for="agree"]'); await page.click('label[for="agree"]');
expect(await page.evaluate(() => globalThis.result.check)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.result.events)).toEqual([ await page.evaluate(() => {
'click', return (globalThis as any).result.check;
'input', })
'change', ).toBe(true);
]); expect(
await page.evaluate(() => {
return (globalThis as any).result.events;
})
).toEqual(['click', 'input', 'change']);
await page.click('label[for="agree"]'); await page.click('label[for="agree"]');
expect(await page.evaluate(() => globalThis.result.check)).toBe(false); expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(false);
}); });
it('should fail to click a missing button', async () => { it('should fail to click a missing button', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
let error = null; let error!: Error;
await page await page.click('button.does-not-exist').catch((error_) => {
.click('button.does-not-exist') return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.message).toBe( expect(error.message).toBe(
'No element found for selector: button.does-not-exist' 'No element found for selector: button.does-not-exist'
); );
@ -218,7 +281,7 @@ describe('Page.click', function () {
it('should not hang with touch-enabled viewports', async () => { it('should not hang with touch-enabled viewports', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
await page.setViewport(puppeteer.devices['iPhone 6'].viewport); await page.setViewport(puppeteer.devices['iPhone 6']!.viewport);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(100, 10); await page.mouse.move(100, 10);
await page.mouse.up(); await page.mouse.up();
@ -229,13 +292,15 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-5'); await page.click('#button-5');
expect( expect(
await page.evaluate(() => document.querySelector('#button-5').textContent) await page.evaluate(() => {
return document.querySelector('#button-5')!.textContent;
})
).toBe('clicked'); ).toBe('clicked');
await page.click('#button-80'); await page.click('#button-80');
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => document.querySelector('#button-80').textContent return document.querySelector('#button-80')!.textContent;
) })
).toBe('clicked'); ).toBe('clicked');
}); });
it('should double click the button', async () => { it('should double click the button', async () => {
@ -243,14 +308,14 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => { await page.evaluate(() => {
globalThis.double = false; (globalThis as any).double = false;
const button = document.querySelector('button'); const button = document.querySelector('button');
button.addEventListener('dblclick', () => { button!.addEventListener('dblclick', () => {
globalThis.double = true; (globalThis as any).double = true;
}); });
}); });
const button = await page.$('button'); const button = (await page.$('button'))!;
await button.click({ clickCount: 2 }); await button!.click({ clickCount: 2 });
expect(await page.evaluate('double')).toBe(true); expect(await page.evaluate('double')).toBe(true);
expect(await page.evaluate('result')).toBe('Clicked'); expect(await page.evaluate('result')).toBe('Clicked');
}); });
@ -260,19 +325,27 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => { await page.evaluate(() => {
const button = document.querySelector('button'); const button = document.querySelector('button');
button.textContent = 'Some really long text that will go offscreen'; button!.textContent = 'Some really long text that will go offscreen';
button.style.position = 'absolute'; button!.style.position = 'absolute';
button.style.left = '368px'; button!.style.left = '368px';
}); });
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should click a rotated button', async () => { it('should click a rotated button', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/rotatedButton.html'); await page.goto(server.PREFIX + '/input/rotatedButton.html');
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should fire contextmenu event on right click', async () => { it('should fire contextmenu event on right click', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -280,7 +353,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'right' }); await page.click('#button-8', { button: 'right' });
expect( expect(
await page.evaluate(() => document.querySelector('#button-8').textContent) await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('context menu'); ).toBe('context menu');
}); });
it('should fire aux event on middle click', async () => { it('should fire aux event on middle click', async () => {
@ -289,7 +364,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'middle' }); await page.click('#button-8', { button: 'middle' });
expect( expect(
await page.evaluate(() => document.querySelector('#button-8').textContent) await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('aux click'); ).toBe('aux click');
}); });
it('should fire back click', async () => { it('should fire back click', async () => {
@ -298,7 +375,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'back' }); await page.click('#button-8', { button: 'back' });
expect( expect(
await page.evaluate(() => document.querySelector('#button-8').textContent) await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('back click'); ).toBe('back click');
}); });
it('should fire forward click', async () => { it('should fire forward click', async () => {
@ -307,7 +386,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'forward' }); await page.click('#button-8', { button: 'forward' });
expect( expect(
await page.evaluate(() => document.querySelector('#button-8').textContent) await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('forward click'); ).toBe('forward click');
}); });
// @see https://github.com/puppeteer/puppeteer/issues/206 // @see https://github.com/puppeteer/puppeteer/issues/206
@ -329,9 +410,13 @@ describe('Page.click', function () {
server.PREFIX + '/input/button.html' server.PREFIX + '/input/button.html'
); );
const frame = page.frames()[1]; const frame = page.frames()[1];
const button = await frame.$('button'); const button = await frame!.$('button');
await button.click(); await button!.click();
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
// @see https://github.com/puppeteer/puppeteer/issues/4110 // @see https://github.com/puppeteer/puppeteer/issues/4110
xit('should click the button with fixed position inside an iframe', async () => { xit('should click the button with fixed position inside an iframe', async () => {
@ -348,17 +433,25 @@ describe('Page.click', function () {
server.CROSS_PROCESS_PREFIX + '/input/button.html' server.CROSS_PROCESS_PREFIX + '/input/button.html'
); );
const frame = page.frames()[1]; const frame = page.frames()[1];
await frame.$eval('button', (button: HTMLElement) => await frame!.$eval('button', (button: Element) => {
button.style.setProperty('position', 'fixed') return (button as HTMLElement).style.setProperty('position', 'fixed');
); });
await frame.click('button'); await frame!.click('button');
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should click the button with deviceScaleFactor set', async () => { it('should click the button with deviceScaleFactor set', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setViewport({ width: 400, height: 400, deviceScaleFactor: 5 }); await page.setViewport({ width: 400, height: 400, deviceScaleFactor: 5 });
expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); expect(
await page.evaluate(() => {
return window.devicePixelRatio;
})
).toBe(5);
await page.setContent('<div style="width:100px;height:100px">spacer</div>'); await page.setContent('<div style="width:100px;height:100px">spacer</div>');
await utils.attachFrame( await utils.attachFrame(
page, page,
@ -366,8 +459,12 @@ describe('Page.click', function () {
server.PREFIX + '/input/button.html' server.PREFIX + '/input/button.html'
); );
const frame = page.frames()[1]; const frame = page.frames()[1];
const button = await frame.$('button'); const button = await frame!.$('button');
await button.click(); await button!.click();
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
}); });

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('Cookie specs', () => { describe('Cookie specs', () => {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -58,36 +58,36 @@ describe('Cookie specs', () => {
}); });
it('should properly report httpOnly cookie', async () => { it('should properly report httpOnly cookie', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.setHeader('Set-Cookie', 'a=b; HttpOnly; Path=/'); res.setHeader('Set-Cookie', 'a=b; HttpOnly; Path=/');
res.end(); res.end();
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies(); const cookies = await page.cookies();
expect(cookies.length).toBe(1); expect(cookies.length).toBe(1);
expect(cookies[0].httpOnly).toBe(true); expect(cookies[0]!.httpOnly).toBe(true);
}); });
it('should properly report "Strict" sameSite cookie', async () => { it('should properly report "Strict" sameSite cookie', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.setHeader('Set-Cookie', 'a=b; SameSite=Strict'); res.setHeader('Set-Cookie', 'a=b; SameSite=Strict');
res.end(); res.end();
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies(); const cookies = await page.cookies();
expect(cookies.length).toBe(1); expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Strict'); expect(cookies[0]!.sameSite).toBe('Strict');
}); });
it('should properly report "Lax" sameSite cookie', async () => { it('should properly report "Lax" sameSite cookie', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.setHeader('Set-Cookie', 'a=b; SameSite=Lax'); res.setHeader('Set-Cookie', 'a=b; SameSite=Lax');
res.end(); res.end();
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies(); const cookies = await page.cookies();
expect(cookies.length).toBe(1); expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Lax'); expect(cookies[0]!.sameSite).toBe('Lax');
}); });
it('should get multiple cookies', async () => { it('should get multiple cookies', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -97,7 +97,9 @@ describe('Cookie specs', () => {
document.cookie = 'password=1234'; document.cookie = 'password=1234';
}); });
const cookies = await page.cookies(); const cookies = await page.cookies();
cookies.sort((a, b) => a.name.localeCompare(b.name)); cookies.sort((a, b) => {
return a.name.localeCompare(b.name);
});
expectCookieEquals(cookies, [ expectCookieEquals(cookies, [
{ {
name: 'password', name: 'password',
@ -149,7 +151,9 @@ describe('Cookie specs', () => {
} }
); );
const cookies = await page.cookies('https://foo.com', 'https://baz.com'); const cookies = await page.cookies('https://foo.com', 'https://baz.com');
cookies.sort((a, b) => a.name.localeCompare(b.name)); cookies.sort((a, b) => {
return a.name.localeCompare(b.name);
});
expectCookieEquals(cookies, [ expectCookieEquals(cookies, [
{ {
name: 'birdo', name: 'birdo',
@ -191,9 +195,11 @@ describe('Cookie specs', () => {
name: 'password', name: 'password',
value: '123456', value: '123456',
}); });
expect(await page.evaluate(() => document.cookie)).toEqual( expect(
'password=123456' await page.evaluate(() => {
); return document.cookie;
})
).toEqual('password=123456');
}); });
itFailsFirefox('should isolate cookies in browser contexts', async () => { itFailsFirefox('should isolate cookies in browser contexts', async () => {
const { page, server, browser } = getTestState(); const { page, server, browser } = getTestState();
@ -211,10 +217,10 @@ describe('Cookie specs', () => {
const cookies2 = await anotherPage.cookies(); const cookies2 = await anotherPage.cookies();
expect(cookies1.length).toBe(1); expect(cookies1.length).toBe(1);
expect(cookies2.length).toBe(1); expect(cookies2.length).toBe(1);
expect(cookies1[0].name).toBe('page1cookie'); expect(cookies1[0]!.name).toBe('page1cookie');
expect(cookies1[0].value).toBe('page1value'); expect(cookies1[0]!.value).toBe('page1value');
expect(cookies2[0].name).toBe('page2cookie'); expect(cookies2[0]!.name).toBe('page2cookie');
expect(cookies2[0].value).toBe('page2value'); expect(cookies2[0]!.value).toBe('page2value');
await anotherContext.close(); await anotherContext.close();
}); });
itFailsFirefox('should set multiple cookies', async () => { itFailsFirefox('should set multiple cookies', async () => {
@ -233,7 +239,11 @@ describe('Cookie specs', () => {
); );
const cookieStrings = await page.evaluate(() => { const cookieStrings = await page.evaluate(() => {
const cookies = document.cookie.split(';'); const cookies = document.cookie.split(';');
return cookies.map((cookie) => cookie.trim()).sort(); return cookies
.map((cookie) => {
return cookie.trim();
})
.sort();
}); });
expect(cookieStrings).toEqual(['foo=bar', 'password=123456']); expect(cookieStrings).toEqual(['foo=bar', 'password=123456']);
@ -247,8 +257,8 @@ describe('Cookie specs', () => {
value: '123456', value: '123456',
}); });
const cookies = await page.cookies(); const cookies = await page.cookies();
expect(cookies[0].session).toBe(true); expect(cookies[0]!.session).toBe(true);
expect(cookies[0].expires).toBe(-1); expect(cookies[0]!.expires).toBe(-1);
}); });
itFailsFirefox('should set cookie with reasonable defaults', async () => { itFailsFirefox('should set cookie with reasonable defaults', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -260,7 +270,9 @@ describe('Cookie specs', () => {
}); });
const cookies = await page.cookies(); const cookies = await page.cookies();
expectCookieEquals( expectCookieEquals(
cookies.sort((a, b) => a.name.localeCompare(b.name)), cookies.sort((a, b) => {
return a.name.localeCompare(b.name);
}),
[ [
{ {
name: 'password', name: 'password',
@ -315,11 +327,11 @@ describe('Cookie specs', () => {
const { page } = getTestState(); const { page } = getTestState();
await page.goto('about:blank'); await page.goto('about:blank');
let error = null; let error!: Error;
try { try {
await page.setCookie({ name: 'example-cookie', value: 'best' }); await page.setCookie({ name: 'example-cookie', value: 'best' });
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toContain( expect(error.message).toContain(
'At least one of the url and domain needs to be specified' 'At least one of the url and domain needs to be specified'
@ -328,7 +340,7 @@ describe('Cookie specs', () => {
it('should not set a cookie with blank page URL', async () => { it('should not set a cookie with blank page URL', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error = null; let error!: Error;
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
try { try {
await page.setCookie( await page.setCookie(
@ -336,7 +348,7 @@ describe('Cookie specs', () => {
{ url: 'about:blank', name: 'example-cookie-blank', value: 'best' } { url: 'about:blank', name: 'example-cookie-blank', value: 'best' }
); );
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toEqual( expect(error.message).toEqual(
`Blank page can not have cookie "example-cookie-blank"` `Blank page can not have cookie "example-cookie-blank"`
@ -345,12 +357,12 @@ describe('Cookie specs', () => {
it('should not set a cookie on a data URL page', async () => { it('should not set a cookie on a data URL page', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page.goto('data:,Hello%2C%20World!'); await page.goto('data:,Hello%2C%20World!');
try { try {
await page.setCookie({ name: 'example-cookie', value: 'best' }); await page.setCookie({ name: 'example-cookie', value: 'best' });
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toContain( expect(error.message).toContain(
'At least one of the url and domain needs to be specified' 'At least one of the url and domain needs to be specified'
@ -369,7 +381,7 @@ describe('Cookie specs', () => {
value: 'bar', value: 'bar',
}); });
const [cookie] = await page.cookies(SECURE_URL); const [cookie] = await page.cookies(SECURE_URL);
expect(cookie.secure).toBe(true); expect(cookie!.secure).toBe(true);
} }
); );
it('should be able to set unsecure cookie for HTTP website', async () => { it('should be able to set unsecure cookie for HTTP website', async () => {
@ -383,7 +395,7 @@ describe('Cookie specs', () => {
value: 'bar', value: 'bar',
}); });
const [cookie] = await page.cookies(HTTP_URL); const [cookie] = await page.cookies(HTTP_URL);
expect(cookie.secure).toBe(false); expect(cookie!.secure).toBe(false);
}); });
itFailsFirefox('should set a cookie on a different domain', async () => { itFailsFirefox('should set a cookie on a different domain', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -419,9 +431,11 @@ describe('Cookie specs', () => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({ name: 'localhost-cookie', value: 'best' }); await page.setCookie({ name: 'localhost-cookie', value: 'best' });
await page.evaluate<(src: string) => Promise<void>>((src) => { await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill; let fulfill!: () => void;
const promise = new Promise<void>((x) => (fulfill = x)); const promise = new Promise<void>((x) => {
const iframe = document.createElement('iframe'); return (fulfill = x);
});
const iframe = document.createElement('iframe') as HTMLIFrameElement;
document.body.appendChild(iframe); document.body.appendChild(iframe);
iframe.onload = fulfill; iframe.onload = fulfill;
iframe.src = src; iframe.src = src;
@ -435,7 +449,7 @@ describe('Cookie specs', () => {
expect(await page.evaluate('document.cookie')).toBe( expect(await page.evaluate('document.cookie')).toBe(
'localhost-cookie=best' 'localhost-cookie=best'
); );
expect(await page.frames()[1].evaluate('document.cookie')).toBe(''); expect(await page.frames()[1]!.evaluate('document.cookie')).toBe('');
expectCookieEquals(await page.cookies(), [ expectCookieEquals(await page.cookies(), [
{ {
@ -487,8 +501,10 @@ describe('Cookie specs', () => {
try { try {
await page.goto(httpsServer.PREFIX + '/grid.html'); await page.goto(httpsServer.PREFIX + '/grid.html');
await page.evaluate<(src: string) => Promise<void>>((src) => { await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill; let fulfill!: () => void;
const promise = new Promise<void>((x) => (fulfill = x)); const promise = new Promise<void>((x) => {
return (fulfill = x);
});
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
document.body.appendChild(iframe); document.body.appendChild(iframe);
iframe.onload = fulfill; iframe.onload = fulfill;
@ -502,7 +518,7 @@ describe('Cookie specs', () => {
sameSite: 'None', sameSite: 'None',
}); });
expect(await page.frames()[1].evaluate('document.cookie')).toBe( expect(await page.frames()[1]!.evaluate('document.cookie')).toBe(
'127-same-site-cookie=best' '127-same-site-cookie=best'
); );
expectCookieEquals( expectCookieEquals(

View File

@ -20,7 +20,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
setupTestBrowserHooks, setupTestBrowserHooks,
describeChromeOnly, describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('Coverage specs', function () { describe('Coverage specs', function () {
describeChromeOnly('JSCoverage', function () { describeChromeOnly('JSCoverage', function () {
@ -35,8 +35,8 @@ describe('Coverage specs', function () {
}); });
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/jscoverage/simple.html'); expect(coverage[0]!.url).toContain('/jscoverage/simple.html');
expect(coverage[0].ranges).toEqual([ expect(coverage[0]!.ranges).toEqual([
{ start: 0, end: 17 }, { start: 0, end: 17 },
{ start: 35, end: 61 }, { start: 35, end: 61 },
]); ]);
@ -48,7 +48,7 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/jscoverage/sourceurl.html'); await page.goto(server.PREFIX + '/jscoverage/sourceurl.html');
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.js'); expect(coverage[0]!.url).toBe('nicename.js');
}); });
it('should ignore eval() scripts by default', async () => { it('should ignore eval() scripts by default', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -65,7 +65,9 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/jscoverage/eval.html'); await page.goto(server.PREFIX + '/jscoverage/eval.html');
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect( expect(
coverage.find((entry) => entry.url.startsWith('debugger://')) coverage.find((entry) => {
return entry.url.startsWith('debugger://');
})
).not.toBe(null); ).not.toBe(null);
expect(coverage.length).toBe(2); expect(coverage.length).toBe(2);
}); });
@ -75,7 +77,9 @@ describe('Coverage specs', function () {
await page.coverage.startJSCoverage({ reportAnonymousScripts: true }); await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate('console.log("foo")'); await page.evaluate('console.log("foo")');
await page.evaluate(() => console.log('bar')); await page.evaluate(() => {
return console.log('bar');
});
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(0); expect(coverage.length).toBe(0);
}); });
@ -86,9 +90,11 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/jscoverage/multiple.html'); await page.goto(server.PREFIX + '/jscoverage/multiple.html');
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(2); expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url)); coverage.sort((a, b) => {
expect(coverage[0].url).toContain('/jscoverage/script1.js'); return a.url.localeCompare(b.url);
expect(coverage[1].url).toContain('/jscoverage/script2.js'); });
expect(coverage[0]!.url).toContain('/jscoverage/script1.js');
expect(coverage[1]!.url).toContain('/jscoverage/script2.js');
}); });
it('should report right ranges', async () => { it('should report right ranges', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -97,9 +103,9 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/jscoverage/ranges.html'); await page.goto(server.PREFIX + '/jscoverage/ranges.html');
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
const entry = coverage[0]; const entry = coverage[0]!;
expect(entry.ranges.length).toBe(1); expect(entry.ranges.length).toBe(1);
const range = entry.ranges[0]; const range = entry.ranges[0]!;
expect(entry.text.substring(range.start, range.end)).toBe( expect(entry.text.substring(range.start, range.end)).toBe(
`console.log('used!');` `console.log('used!');`
); );
@ -111,7 +117,7 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/jscoverage/unused.html'); await page.goto(server.PREFIX + '/jscoverage/unused.html');
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
const entry = coverage[0]; const entry = coverage[0]!;
expect(entry.url).toContain('unused.html'); expect(entry.url).toContain('unused.html');
expect(entry.ranges.length).toBe(0); expect(entry.ranges.length).toBe(0);
}); });
@ -166,7 +172,7 @@ describe('Coverage specs', function () {
}); });
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].rawScriptCoverage).toBeUndefined(); expect(coverage[0]!.rawScriptCoverage).toBeUndefined();
}); });
it('should include rawScriptCoverage field when enabled', async () => { it('should include rawScriptCoverage field when enabled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -178,7 +184,7 @@ describe('Coverage specs', function () {
}); });
const coverage = await page.coverage.stopJSCoverage(); const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].rawScriptCoverage).toBeTruthy(); expect(coverage[0]!.rawScriptCoverage).toBeTruthy();
}); });
}); });
// @see https://crbug.com/990945 // @see https://crbug.com/990945
@ -205,10 +211,10 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/csscoverage/simple.html'); await page.goto(server.PREFIX + '/csscoverage/simple.html');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/simple.html'); expect(coverage[0]!.url).toContain('/csscoverage/simple.html');
expect(coverage[0].ranges).toEqual([{ start: 1, end: 22 }]); expect(coverage[0]!.ranges).toEqual([{ start: 1, end: 22 }]);
const range = coverage[0].ranges[0]; const range = coverage[0]!.ranges[0]!;
expect(coverage[0].text.substring(range.start, range.end)).toBe( expect(coverage[0]!.text.substring(range.start, range.end)).toBe(
'div { color: green; }' 'div { color: green; }'
); );
}); });
@ -219,7 +225,7 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/csscoverage/sourceurl.html'); await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.css'); expect(coverage[0]!.url).toBe('nicename.css');
}); });
it('should report multiple stylesheets', async () => { it('should report multiple stylesheets', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -228,9 +234,11 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/csscoverage/multiple.html'); await page.goto(server.PREFIX + '/csscoverage/multiple.html');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(2); expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url)); coverage.sort((a, b) => {
expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css'); return a.url.localeCompare(b.url);
expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css'); });
expect(coverage[0]!.url).toContain('/csscoverage/stylesheet1.css');
expect(coverage[1]!.url).toContain('/csscoverage/stylesheet2.css');
}); });
it('should report stylesheets that have no coverage', async () => { it('should report stylesheets that have no coverage', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -239,8 +247,8 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/csscoverage/unused.html'); await page.goto(server.PREFIX + '/csscoverage/unused.html');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('unused.css'); expect(coverage[0]!.url).toBe('unused.css');
expect(coverage[0].ranges.length).toBe(0); expect(coverage[0]!.ranges.length).toBe(0);
}); });
it('should work with media queries', async () => { it('should work with media queries', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -249,8 +257,8 @@ describe('Coverage specs', function () {
await page.goto(server.PREFIX + '/csscoverage/media.html'); await page.goto(server.PREFIX + '/csscoverage/media.html');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/media.html'); expect(coverage[0]!.url).toContain('/csscoverage/media.html');
expect(coverage[0].ranges).toEqual([{ start: 17, end: 38 }]); expect(coverage[0]!.ranges).toEqual([{ start: 17, end: 38 }]);
}); });
it('should work with complicated usecases', async () => { it('should work with complicated usecases', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -268,9 +276,9 @@ describe('Coverage specs', function () {
await page.coverage.startCSSCoverage(); await page.coverage.startCSSCoverage();
await page.addStyleTag({ content: 'body { margin: 10px;}' }); await page.addStyleTag({ content: 'body { margin: 10px;}' });
// trigger style recalc // trigger style recalc
const margin = await page.evaluate( const margin = await page.evaluate(() => {
() => window.getComputedStyle(document.body).margin return window.getComputedStyle(document.body).margin;
); });
expect(margin).toBe('10px'); expect(margin).toBe('10px');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(0); expect(coverage.length).toBe(0);
@ -286,7 +294,9 @@ describe('Coverage specs', function () {
link.rel = 'stylesheet'; link.rel = 'stylesheet';
link.href = url; link.href = url;
document.head.appendChild(link); document.head.appendChild(link);
await new Promise((x) => (link.onload = x)); await new Promise((x) => {
return (link.onload = x);
});
}, server.PREFIX + '/csscoverage/stylesheet1.css'); }, server.PREFIX + '/csscoverage/stylesheet1.css');
const coverage = await page.coverage.stopCSSCoverage(); const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1); expect(coverage.length).toBe(1);

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('DefaultBrowserContext', function () { describe('DefaultBrowserContext', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -57,9 +57,11 @@ describe('DefaultBrowserContext', function () {
name: 'username', name: 'username',
value: 'John Doe', value: 'John Doe',
}); });
expect(await page.evaluate(() => document.cookie)).toBe( expect(
'username=John Doe' await page.evaluate(() => {
); return document.cookie;
})
).toBe('username=John Doe');
expectCookieEquals(await page.cookies(), [ expectCookieEquals(await page.cookies(), [
{ {
name: 'username', name: 'username',

View File

@ -21,7 +21,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
setupTestBrowserHooks, setupTestBrowserHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('Page.Events.Dialog', function () { describe('Page.Events.Dialog', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -35,10 +35,12 @@ describe('Page.Events.Dialog', function () {
}); });
page.on('dialog', onDialog); page.on('dialog', onDialog);
await page.evaluate(() => alert('yo')); await page.evaluate(() => {
return alert('yo');
});
expect(onDialog.callCount).toEqual(1); expect(onDialog.callCount).toEqual(1);
const dialog = onDialog.firstCall.args[0]; const dialog = onDialog.firstCall.args[0]!;
expect(dialog.type()).toBe('alert'); expect(dialog.type()).toBe('alert');
expect(dialog.defaultValue()).toBe(''); expect(dialog.defaultValue()).toBe('');
expect(dialog.message()).toBe('yo'); expect(dialog.message()).toBe('yo');
@ -52,10 +54,12 @@ describe('Page.Events.Dialog', function () {
}); });
page.on('dialog', onDialog); page.on('dialog', onDialog);
const result = await page.evaluate(() => prompt('question?', 'yes.')); const result = await page.evaluate(() => {
return prompt('question?', 'yes.');
});
expect(onDialog.callCount).toEqual(1); expect(onDialog.callCount).toEqual(1);
const dialog = onDialog.firstCall.args[0]; const dialog = onDialog.firstCall.args[0]!;
expect(dialog.type()).toBe('prompt'); expect(dialog.type()).toBe('prompt');
expect(dialog.defaultValue()).toBe('yes.'); expect(dialog.defaultValue()).toBe('yes.');
expect(dialog.message()).toBe('question?'); expect(dialog.message()).toBe('question?');
@ -68,7 +72,9 @@ describe('Page.Events.Dialog', function () {
page.on('dialog', (dialog) => { page.on('dialog', (dialog) => {
dialog.dismiss(); dialog.dismiss();
}); });
const result = await page.evaluate(() => prompt('question?')); const result = await page.evaluate(() => {
return prompt('question?');
});
expect(result).toBe(null); expect(result).toBe(null);
}); });
}); });

View File

@ -20,7 +20,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
setupTestBrowserHooks, setupTestBrowserHooks,
describeChromeOnly, describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describeChromeOnly('Input.drag', function () { describeChromeOnly('Input.drag', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -29,12 +29,14 @@ describeChromeOnly('Input.drag', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/drag-and-drop.html'); await page.goto(server.PREFIX + '/input/drag-and-drop.html');
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
try { try {
await draggable.drag({ x: 1, y: 1 }); await draggable!.drag({ x: 1, y: 1 });
} catch (error) { } catch (error) {
expect(error.message).toContain('Drag Interception is not enabled!'); expect((error as Error).message).toContain(
'Drag Interception is not enabled!'
);
} }
}); });
it('should emit a dragIntercepted event when dragged', async () => { it('should emit a dragIntercepted event when dragged', async () => {
@ -44,11 +46,15 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
const data = await draggable.drag({ x: 1, y: 1 }); const data = await draggable.drag({ x: 1, y: 1 });
expect(data.items.length).toBe(1); expect(data.items.length).toBe(1);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true); expect(
await page.evaluate(() => {
return (globalThis as any).didDragStart;
})
).toBe(true);
}); });
it('should emit a dragEnter', async () => { it('should emit a dragEnter', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -57,13 +63,21 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
const data = await draggable.drag({ x: 1, y: 1 }); const data = await draggable.drag({ x: 1, y: 1 });
const dropzone = await page.$('#drop'); const dropzone = (await page.$('#drop'))!;
await dropzone.dragEnter(data); await dropzone.dragEnter(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true); await page.evaluate(() => {
return (globalThis as any).didDragStart;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragEnter;
})
).toBe(true);
}); });
it('should emit a dragOver event', async () => { it('should emit a dragOver event', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -72,15 +86,27 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
const data = await draggable.drag({ x: 1, y: 1 }); const data = await draggable.drag({ x: 1, y: 1 });
const dropzone = await page.$('#drop'); const dropzone = (await page.$('#drop'))!;
await dropzone.dragEnter(data); await dropzone.dragEnter(data);
await dropzone.dragOver(data); await dropzone.dragOver(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true); await page.evaluate(() => {
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true); return (globalThis as any).didDragStart;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragEnter;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragOver;
})
).toBe(true);
}); });
it('can be dropped', async () => { it('can be dropped', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -89,17 +115,33 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
const dropzone = await page.$('#drop'); const dropzone = (await page.$('#drop'))!;
const data = await draggable.drag({ x: 1, y: 1 }); const data = await draggable.drag({ x: 1, y: 1 });
await dropzone.dragEnter(data); await dropzone.dragEnter(data);
await dropzone.dragOver(data); await dropzone.dragOver(data);
await dropzone.drop(data); await dropzone.drop(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true); await page.evaluate(() => {
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true); return (globalThis as any).didDragStart;
expect(await page.evaluate(() => globalThis.didDrop)).toBe(true); })
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragEnter;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragOver;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDrop;
})
).toBe(true);
}); });
it('can be dragged and dropped with a single function', async () => { it('can be dragged and dropped with a single function', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -108,14 +150,30 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
const dropzone = await page.$('#drop'); const dropzone = (await page.$('#drop'))!;
await draggable.dragAndDrop(dropzone); await draggable.dragAndDrop(dropzone);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true); expect(
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true); await page.evaluate(() => {
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true); return (globalThis as any).didDragStart;
expect(await page.evaluate(() => globalThis.didDrop)).toBe(true); })
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragEnter;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDragOver;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).didDrop;
})
).toBe(true);
}); });
it('can be disabled', async () => { it('can be disabled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -124,14 +182,16 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false); expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true); await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true); expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag'); const draggable = (await page.$('#drag'))!;
await draggable.drag({ x: 1, y: 1 }); await draggable.drag({ x: 1, y: 1 });
await page.setDragInterception(false); await page.setDragInterception(false);
try { try {
await draggable.drag({ x: 1, y: 1 }); await draggable.drag({ x: 1, y: 1 });
} catch (error) { } catch (error) {
expect(error.message).toContain('Drag Interception is not enabled!'); expect((error as Error).message).toContain(
'Drag Interception is not enabled!'
);
} }
}); });
}); });

View File

@ -22,7 +22,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import utils from './utils.js'; import utils from './utils.js';
import { ElementHandle } from '../../lib/cjs/puppeteer/common/JSHandle.js'; import { ElementHandle } from '../../lib/cjs/puppeteer/common/JSHandle.js';
@ -37,7 +37,7 @@ describe('ElementHandle specs', function () {
await page.setViewport({ width: 500, height: 500 }); await page.setViewport({ width: 500, height: 500 });
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const elementHandle = await page.$('.box:nth-of-type(13)'); const elementHandle = (await page.$('.box:nth-of-type(13)'))!;
const box = await elementHandle.boundingBox(); const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 }); expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
}); });
@ -46,8 +46,8 @@ describe('ElementHandle specs', function () {
await page.setViewport({ width: 500, height: 500 }); await page.setViewport({ width: 500, height: 500 });
await page.goto(server.PREFIX + '/frames/nested-frames.html'); await page.goto(server.PREFIX + '/frames/nested-frames.html');
const nestedFrame = page.frames()[1].childFrames()[1]; const nestedFrame = page.frames()[1]!.childFrames()[1]!;
const elementHandle = await nestedFrame.$('div'); const elementHandle = (await nestedFrame.$('div'))!;
const box = await elementHandle.boundingBox(); const box = await elementHandle.boundingBox();
if (isChrome) { if (isChrome) {
expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 }); expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 });
@ -59,7 +59,7 @@ describe('ElementHandle specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<div style="display:none">hi</div>'); await page.setContent('<div style="display:none">hi</div>');
const element = await page.$('div'); const element = (await page.$('div'))!;
expect(await element.boundingBox()).toBe(null); expect(await element.boundingBox()).toBe(null);
}); });
it('should force a layout', async () => { it('should force a layout', async () => {
@ -69,11 +69,10 @@ describe('ElementHandle specs', function () {
await page.setContent( await page.setContent(
'<div style="width: 100px; height: 100px">hello</div>' '<div style="width: 100px; height: 100px">hello</div>'
); );
const elementHandle = await page.$('div'); const elementHandle = (await page.$('div'))!;
await page.evaluate<(element: HTMLElement) => void>( await page.evaluate((element: HTMLElement) => {
(element) => (element.style.height = '200px'), return (element.style.height = '200px');
elementHandle }, elementHandle);
);
const box = await elementHandle.boundingBox(); const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 });
}); });
@ -85,7 +84,7 @@ describe('ElementHandle specs', function () {
<rect id="theRect" x="30" y="50" width="200" height="300"></rect> <rect id="theRect" x="30" y="50" width="200" height="300"></rect>
</svg> </svg>
`); `);
const element = await page.$('#therect'); const element = (await page.$('#therect'))!;
const pptrBoundingBox = await element.boundingBox(); const pptrBoundingBox = await element.boundingBox();
const webBoundingBox = await page.evaluate((e: HTMLElement) => { const webBoundingBox = await page.evaluate((e: HTMLElement) => {
const rect = e.getBoundingClientRect(); const rect = e.getBoundingClientRect();
@ -104,14 +103,14 @@ describe('ElementHandle specs', function () {
// Step 1: Add Frame and position it absolutely. // Step 1: Add Frame and position it absolutely.
await utils.attachFrame(page, 'frame1', server.PREFIX + '/resetcss.html'); await utils.attachFrame(page, 'frame1', server.PREFIX + '/resetcss.html');
await page.evaluate(() => { await page.evaluate(() => {
const frame = document.querySelector<HTMLElement>('#frame1'); const frame = document.querySelector<HTMLElement>('#frame1')!;
frame.style.position = 'absolute'; frame.style.position = 'absolute';
frame.style.left = '1px'; frame.style.left = '1px';
frame.style.top = '2px'; frame.style.top = '2px';
}); });
// Step 2: Add div and position it absolutely inside frame. // Step 2: Add div and position it absolutely inside frame.
const frame = page.frames()[1]; const frame = page.frames()[1]!;
const divHandle = ( const divHandle = (
await frame.evaluateHandle(() => { await frame.evaluateHandle(() => {
const div = document.createElement('div'); const div = document.createElement('div');
@ -127,25 +126,25 @@ describe('ElementHandle specs', function () {
div.style.height = '7px'; div.style.height = '7px';
return div; return div;
}) })
).asElement(); ).asElement()!;
// Step 3: query div's boxModel and assert box values. // Step 3: query div's boxModel and assert box values.
const box = await divHandle.boxModel(); const box = (await divHandle.boxModel())!;
expect(box.width).toBe(6); expect(box.width).toBe(6);
expect(box.height).toBe(7); expect(box.height).toBe(7);
expect(box.margin[0]).toEqual({ expect(box.margin[0]!).toEqual({
x: 1 + 4, // frame.left + div.left x: 1 + 4, // frame.left + div.left
y: 2 + 5, y: 2 + 5,
}); });
expect(box.border[0]).toEqual({ expect(box.border[0]!).toEqual({
x: 1 + 4 + 3, // frame.left + div.left + div.margin-left x: 1 + 4 + 3, // frame.left + div.left + div.margin-left
y: 2 + 5, y: 2 + 5,
}); });
expect(box.padding[0]).toEqual({ expect(box.padding[0]!).toEqual({
x: 1 + 4 + 3 + 1, // frame.left + div.left + div.marginLeft + div.borderLeft x: 1 + 4 + 3 + 1, // frame.left + div.left + div.marginLeft + div.borderLeft
y: 2 + 5, y: 2 + 5,
}); });
expect(box.content[0]).toEqual({ expect(box.content[0]!).toEqual({
x: 1 + 4 + 3 + 1 + 2, // frame.left + div.left + div.marginLeft + div.borderLeft + dif.paddingLeft x: 1 + 4 + 3 + 1 + 2, // frame.left + div.left + div.marginLeft + div.borderLeft + dif.paddingLeft
y: 2 + 5, y: 2 + 5,
}); });
@ -155,7 +154,7 @@ describe('ElementHandle specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<div style="display:none">hi</div>'); await page.setContent('<div style="display:none">hi</div>');
const element = await page.$('div'); const element = (await page.$('div'))!;
expect(await element.boxModel()).toBe(null); expect(await element.boxModel()).toBe(null);
}); });
}); });
@ -166,9 +165,9 @@ describe('ElementHandle specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const elementHandle = await page.$('#frame1'); const elementHandle = (await page.$('#frame1'))!;
const frame = await elementHandle.contentFrame(); const frame = await elementHandle.contentFrame();
expect(frame).toBe(page.frames()[1]); expect(frame).toBe(page.frames()[1]!);
}); });
}); });
@ -178,57 +177,68 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await button.click(); await button.click();
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should work for Shadow DOM v1', async () => { it('should work for Shadow DOM v1', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/shadow.html'); await page.goto(server.PREFIX + '/shadow.html');
const buttonHandle = await page.evaluateHandle<ElementHandle>( const buttonHandle = await page.evaluateHandle<ElementHandle>(() => {
// @ts-expect-error button is expected to be in the page's scope. // @ts-expect-error button is expected to be in the page's scope.
() => button return button;
); });
await buttonHandle.click(); await buttonHandle.click();
expect( expect(
await page.evaluate( await page.evaluate(() => {
// @ts-expect-error clicked is expected to be in the page's scope. // @ts-expect-error clicked is expected to be in the page's scope.
() => clicked return clicked;
) })
).toBe(true); ).toBe(true);
}); });
it('should work for TextNodes', async () => { it('should work for TextNodes', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle<ElementHandle>( const buttonTextNode = await page.evaluateHandle<ElementHandle>(() => {
() => document.querySelector('button').firstChild return document.querySelector('button')!.firstChild;
); });
let error = null; let error!: Error;
await buttonTextNode.click().catch((error_) => (error = error_)); await buttonTextNode.click().catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Node is not of type HTMLElement'); expect(error.message).toBe('Node is not of type HTMLElement');
}); });
it('should throw for detached nodes', async () => { it('should throw for detached nodes', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await page.evaluate((button: HTMLElement) => button.remove(), button); await page.evaluate((button: HTMLElement) => {
let error = null; return button.remove();
await button.click().catch((error_) => (error = error_)); }, button);
let error!: Error;
await button.click().catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Node is detached from document'); expect(error.message).toBe('Node is detached from document');
}); });
it('should throw for hidden nodes', async () => { it('should throw for hidden nodes', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await page.evaluate( await page.evaluate((button: HTMLElement) => {
(button: HTMLElement) => (button.style.display = 'none'), return (button.style.display = 'none');
button }, button);
); const error = await button.click().catch((error_) => {
const error = await button.click().catch((error_) => error_); return error_;
});
expect(error.message).toBe( expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement' 'Node is either not clickable or not an HTMLElement'
); );
@ -237,12 +247,13 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await page.evaluate( await page.evaluate((button: HTMLElement) => {
(button: HTMLElement) => (button.parentElement.style.display = 'none'), return (button.parentElement!.style.display = 'none');
button }, button);
); const error = await button.click().catch((error_) => {
const error = await button.click().catch((error_) => error_); return error_;
});
expect(error.message).toBe( expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement' 'Node is either not clickable or not an HTMLElement'
); );
@ -251,8 +262,10 @@ describe('ElementHandle specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('hello<br>goodbye'); await page.setContent('hello<br>goodbye');
const br = await page.$('br'); const br = (await page.$('br'))!;
const error = await br.click().catch((error_) => error_); const error = await br.click().catch((error_) => {
return error_;
});
expect(error.message).toBe( expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement' 'Node is either not clickable or not an HTMLElement'
); );
@ -267,17 +280,19 @@ describe('ElementHandle specs', function () {
await page.setContent( await page.setContent(
'<div id="not-foo"></div><div class="bar">bar2</div><div class="foo">Foo1</div>' '<div id="not-foo"></div><div class="bar">bar2</div><div class="foo">Foo1</div>'
); );
let element = await waitFor; let element = (await waitFor)!;
expect(element).toBeDefined(); expect(element).toBeDefined();
const innerWaitFor = element.waitForSelector('.bar'); const innerWaitFor = element.waitForSelector('.bar');
await element.evaluate((el) => { await element.evaluate((el) => {
el.innerHTML = '<div class="bar">bar1</div>'; el.innerHTML = '<div class="bar">bar1</div>';
}); });
element = await innerWaitFor; element = (await innerWaitFor)!;
expect(element).toBeDefined(); expect(element).toBeDefined();
expect( expect(
await element.evaluate((el: HTMLElement) => el.innerText) await element.evaluate((el) => {
return (el as HTMLElement).innerText;
})
).toStrictEqual('bar1'); ).toStrictEqual('bar1');
}); });
}); });
@ -298,14 +313,18 @@ describe('ElementHandle specs', function () {
</div>` </div>`
); );
const el2 = await page.waitForSelector('#el1'); const el2 = (await page.waitForSelector('#el1'))!;
expect( expect(
await (await el2.waitForXPath('//div')).evaluate((el) => el.id) await (await el2.waitForXPath('//div'))!.evaluate((el) => {
return el.id;
})
).toStrictEqual('el2'); ).toStrictEqual('el2');
expect( expect(
await (await el2.waitForXPath('.//div')).evaluate((el) => el.id) await (await el2.waitForXPath('.//div'))!.evaluate((el) => {
return el.id;
})
).toStrictEqual('el2'); ).toStrictEqual('el2');
}); });
}); });
@ -315,10 +334,12 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
const button = await page.$('#button-6'); const button = (await page.$('#button-6'))!;
await button.hover(); await button.hover();
expect( expect(
await page.evaluate(() => document.querySelector('button:hover').id) await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6'); ).toBe('button-6');
}); });
}); });
@ -329,7 +350,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
for (let i = 0; i < 11; ++i) { for (let i = 0; i < 11; ++i) {
const button = await page.$('#btn' + i); const button = (await page.$('#btn' + i))!;
// All but last button are visible. // All but last button are visible.
const visible = i < 10; const visible = i < 10;
expect(await button.isIntersectingViewport()).toBe(visible); expect(await button.isIntersectingViewport()).toBe(visible);
@ -341,7 +362,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
// a button almost cannot be seen // a button almost cannot be seen
// sometimes we expect to return false by isIntersectingViewport1 // sometimes we expect to return false by isIntersectingViewport1
const button = await page.$('#btn11'); const button = (await page.$('#btn11'))!;
expect( expect(
await button.isIntersectingViewport({ await button.isIntersectingViewport({
threshold: 0.001, threshold: 0.001,
@ -354,7 +375,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
// a button almost cannot be seen // a button almost cannot be seen
// sometimes we expect to return false by isIntersectingViewport1 // sometimes we expect to return false by isIntersectingViewport1
const button = await page.$('#btn0'); const button = (await page.$('#btn0'))!;
expect( expect(
await button.isIntersectingViewport({ await button.isIntersectingViewport({
threshold: 1, threshold: 1,
@ -374,15 +395,15 @@ describe('ElementHandle specs', function () {
// Register. // Register.
puppeteer.registerCustomQueryHandler('getById', { puppeteer.registerCustomQueryHandler('getById', {
queryOne: (element, selector) => queryOne: (_element, selector) => {
document.querySelector(`[id="${selector}"]`), return document.querySelector(`[id="${selector}"]`);
},
}); });
const element = await page.$('getById/foo'); const element = (await page.$('getById/foo'))!;
expect( expect(
await page.evaluate<(element: HTMLElement) => string>( await page.evaluate<(element: HTMLElement) => string>((element) => {
(element) => element.id, return element.id;
element }, element)
)
).toBe('foo'); ).toBe('foo');
const handlerNamesAfterRegistering = puppeteer.customQueryHandlerNames(); const handlerNamesAfterRegistering = puppeteer.customQueryHandlerNames();
expect(handlerNamesAfterRegistering.includes('getById')).toBeTruthy(); expect(handlerNamesAfterRegistering.includes('getById')).toBeTruthy();
@ -407,7 +428,9 @@ describe('ElementHandle specs', function () {
try { try {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('1/2/3', { puppeteer.registerCustomQueryHandler('1/2/3', {
queryOne: () => document.querySelector('foo'), queryOne: () => {
return document.querySelector('foo');
},
}); });
throw new Error( throw new Error(
'Custom query handler name was invalid - throw expected' 'Custom query handler name was invalid - throw expected'
@ -424,18 +447,20 @@ describe('ElementHandle specs', function () {
'<div id="not-foo"></div><div class="foo">Foo1</div><div class="foo baz">Foo2</div>' '<div id="not-foo"></div><div class="foo">Foo1</div><div class="foo baz">Foo2</div>'
); );
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryAll: (element, selector) => queryAll: (_element, selector) => {
document.querySelectorAll(`.${selector}`), return document.querySelectorAll(`.${selector}`);
},
}); });
const elements = await page.$$('getByClass/foo'); const elements = await page.$$('getByClass/foo');
const classNames = await Promise.all( const classNames = await Promise.all(
elements.map( elements.map(async (element) => {
async (element) => return await page.evaluate<(element: HTMLElement) => string>(
await page.evaluate<(element: HTMLElement) => string>( (element) => {
(element) => element.className, return element.className;
},
element element
) );
) })
); );
expect(classNames).toStrictEqual(['foo', 'foo baz']); expect(classNames).toStrictEqual(['foo', 'foo baz']);
@ -446,20 +471,22 @@ describe('ElementHandle specs', function () {
'<div id="not-foo"></div><div class="foo">Foo1</div><div class="foo baz">Foo2</div>' '<div id="not-foo"></div><div class="foo">Foo1</div><div class="foo baz">Foo2</div>'
); );
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryAll: (element, selector) => queryAll: (_element, selector) => {
document.querySelectorAll(`.${selector}`), return document.querySelectorAll(`.${selector}`);
},
});
const elements = await page.$$eval('getByClass/foo', (divs) => {
return divs.length;
}); });
const elements = await page.$$eval(
'getByClass/foo',
(divs) => divs.length
);
expect(elements).toBe(2); expect(elements).toBe(2);
}); });
it('should wait correctly with waitForSelector', async () => { it('should wait correctly with waitForSelector', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`), queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
}); });
const waitFor = page.waitForSelector('getByClass/foo'); const waitFor = page.waitForSelector('getByClass/foo');
@ -475,7 +502,9 @@ describe('ElementHandle specs', function () {
it('should wait correctly with waitForSelector on an element', async () => { it('should wait correctly with waitForSelector on an element', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`), queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
}); });
const waitFor = page.waitForSelector('getByClass/foo'); const waitFor = page.waitForSelector('getByClass/foo');
@ -483,7 +512,7 @@ describe('ElementHandle specs', function () {
await page.setContent( await page.setContent(
'<div id="not-foo"></div><div class="bar">bar2</div><div class="foo">Foo1</div>' '<div id="not-foo"></div><div class="bar">bar2</div><div class="foo">Foo1</div>'
); );
let element = await waitFor; let element = (await waitFor)!;
expect(element).toBeDefined(); expect(element).toBeDefined();
const innerWaitFor = element.waitForSelector('getByClass/bar'); const innerWaitFor = element.waitForSelector('getByClass/bar');
@ -492,10 +521,12 @@ describe('ElementHandle specs', function () {
el.innerHTML = '<div class="bar">bar1</div>'; el.innerHTML = '<div class="bar">bar1</div>';
}); });
element = await innerWaitFor; element = (await innerWaitFor)!;
expect(element).toBeDefined(); expect(element).toBeDefined();
expect( expect(
await element.evaluate((el: HTMLElement) => el.innerText) await element.evaluate((el) => {
return (el as HTMLElement).innerText;
})
).toStrictEqual('bar1'); ).toStrictEqual('bar1');
}); });
@ -504,7 +535,9 @@ describe('ElementHandle specs', function () {
sinon.stub(console, 'warn').callsFake(() => {}); sinon.stub(console, 'warn').callsFake(() => {});
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`), queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
}); });
const waitFor = page.waitFor('getByClass/foo'); const waitFor = page.waitFor('getByClass/foo');
@ -522,12 +555,15 @@ describe('ElementHandle specs', function () {
'<div id="not-foo"></div><div class="foo"><div id="nested-foo" class="foo"/></div><div class="foo baz">Foo2</div>' '<div id="not-foo"></div><div class="foo"><div id="nested-foo" class="foo"/></div><div class="foo baz">Foo2</div>'
); );
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`), queryOne: (element, selector) => {
queryAll: (element, selector) => return element.querySelector(`.${selector}`);
element.querySelectorAll(`.${selector}`), },
queryAll: (element, selector) => {
return element.querySelectorAll(`.${selector}`);
},
}); });
const element = await page.$('getByClass/foo'); const element = (await page.$('getByClass/foo'))!;
expect(element).toBeDefined(); expect(element).toBeDefined();
const elements = await page.$$('getByClass/foo'); const elements = await page.$$('getByClass/foo');
@ -539,20 +575,26 @@ describe('ElementHandle specs', function () {
'<div id="not-foo"></div><div class="foo">text</div><div class="foo baz">content</div>' '<div id="not-foo"></div><div class="foo">text</div><div class="foo baz">content</div>'
); );
puppeteer.registerCustomQueryHandler('getByClass', { puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`), queryOne: (element, selector) => {
queryAll: (element, selector) => return element.querySelector(`.${selector}`);
element.querySelectorAll(`.${selector}`), },
queryAll: (element, selector) => {
return element.querySelectorAll(`.${selector}`);
},
}); });
const txtContent = await page.$eval( const txtContent = await page.$eval('getByClass/foo', (div) => {
'getByClass/foo', return div.textContent;
(div) => div.textContent });
);
expect(txtContent).toBe('text'); expect(txtContent).toBe('text');
const txtContents = await page.$$eval('getByClass/foo', (divs) => const txtContents = await page.$$eval('getByClass/foo', (divs) => {
divs.map((d) => d.textContent).join('') return divs
); .map((d) => {
return d.textContent;
})
.join('');
});
expect(txtContents).toBe('textcontent'); expect(txtContents).toBe('textcontent');
}); });
}); });

View File

@ -15,24 +15,25 @@
*/ */
import expect from 'expect'; import expect from 'expect';
import { Device } from '../../lib/cjs/puppeteer/common/DeviceDescriptors.js';
import { import {
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('Emulation', () => { describe('Emulation', () => {
setupTestBrowserHooks(); setupTestBrowserHooks();
setupTestPageAndContextHooks(); setupTestPageAndContextHooks();
let iPhone; let iPhone!: Device;
let iPhoneLandscape; let iPhoneLandscape!: Device;
before(() => { before(() => {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
iPhone = puppeteer.devices['iPhone 6']; iPhone = puppeteer.devices['iPhone 6']!;
iPhoneLandscape = puppeteer.devices['iPhone 6 landscape']; iPhoneLandscape = puppeteer.devices['iPhone 6 landscape']!;
}); });
describe('Page.viewport', function () { describe('Page.viewport', function () {
@ -47,26 +48,52 @@ describe('Emulation', () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/mobile.html'); await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(800); expect(
await page.evaluate(() => {
return window.innerWidth;
})
).toBe(800);
await page.setViewport(iPhone.viewport); await page.setViewport(iPhone.viewport);
expect(await page.evaluate(() => window.innerWidth)).toBe(375); expect(
await page.evaluate(() => {
return window.innerWidth;
})
).toBe(375);
await page.setViewport({ width: 400, height: 300 }); await page.setViewport({ width: 400, height: 300 });
expect(await page.evaluate(() => window.innerWidth)).toBe(400); expect(
await page.evaluate(() => {
return window.innerWidth;
})
).toBe(400);
}); });
it('should support touch emulation', async () => { it('should support touch emulation', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/mobile.html'); await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); expect(
await page.evaluate(() => {
return 'ontouchstart' in window;
})
).toBe(false);
await page.setViewport(iPhone.viewport); await page.setViewport(iPhone.viewport);
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); expect(
await page.evaluate(() => {
return 'ontouchstart' in window;
})
).toBe(true);
expect(await page.evaluate(dispatchTouch)).toBe('Received touch'); expect(await page.evaluate(dispatchTouch)).toBe('Received touch');
await page.setViewport({ width: 100, height: 100 }); await page.setViewport({ width: 100, height: 100 });
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); expect(
await page.evaluate(() => {
return 'ontouchstart' in window;
})
).toBe(false);
function dispatchTouch() { function dispatchTouch() {
let fulfill; let fulfill!: (value: string) => void;
const promise = new Promise((x) => (fulfill = x)); const promise = new Promise((x) => {
fulfill = x;
});
window.ontouchstart = () => { window.ontouchstart = () => {
fulfill('Received touch'); fulfill('Received touch');
}; };
@ -81,39 +108,51 @@ describe('Emulation', () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/detect-touch.html'); await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe( expect(
'NO' await page.evaluate(() => {
); return document.body.textContent!.trim();
})
).toBe('NO');
await page.setViewport(iPhone.viewport); await page.setViewport(iPhone.viewport);
await page.goto(server.PREFIX + '/detect-touch.html'); await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe( expect(
'YES' await page.evaluate(() => {
); return document.body.textContent!.trim();
})
).toBe('YES');
}); });
it('should detect touch when applying viewport with touches', async () => { it('should detect touch when applying viewport with touches', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setViewport({ width: 800, height: 600, hasTouch: true }); await page.setViewport({ width: 800, height: 600, hasTouch: true });
await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' }); await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' });
expect(await page.evaluate(() => globalThis.Modernizr.touchevents)).toBe( expect(
true await page.evaluate(() => {
); return (globalThis as any).Modernizr.touchevents;
})
).toBe(true);
}); });
itFailsFirefox('should support landscape emulation', async () => { itFailsFirefox('should support landscape emulation', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/mobile.html'); await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => screen.orientation.type)).toBe( expect(
'portrait-primary' await page.evaluate(() => {
); return screen.orientation.type;
})
).toBe('portrait-primary');
await page.setViewport(iPhoneLandscape.viewport); await page.setViewport(iPhoneLandscape.viewport);
expect(await page.evaluate(() => screen.orientation.type)).toBe( expect(
'landscape-primary' await page.evaluate(() => {
); return screen.orientation.type;
})
).toBe('landscape-primary');
await page.setViewport({ width: 100, height: 100 }); await page.setViewport({ width: 100, height: 100 });
expect(await page.evaluate(() => screen.orientation.type)).toBe( expect(
'portrait-primary' await page.evaluate(() => {
); return screen.orientation.type;
})
).toBe('portrait-primary');
}); });
}); });
@ -123,23 +162,32 @@ describe('Emulation', () => {
await page.goto(server.PREFIX + '/mobile.html'); await page.goto(server.PREFIX + '/mobile.html');
await page.emulate(iPhone); await page.emulate(iPhone);
expect(await page.evaluate(() => window.innerWidth)).toBe(375); expect(
expect(await page.evaluate(() => navigator.userAgent)).toContain( await page.evaluate(() => {
'iPhone' return window.innerWidth;
); })
).toBe(375);
expect(
await page.evaluate(() => {
return navigator.userAgent;
})
).toContain('iPhone');
}); });
itFailsFirefox('should support clicking', async () => { itFailsFirefox('should support clicking', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.emulate(iPhone); await page.emulate(iPhone);
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await page.evaluate( await page.evaluate((button: HTMLElement) => {
(button: HTMLElement) => (button.style.marginTop = '200px'), return (button.style.marginTop = '200px');
button }, button);
);
await button.click(); await button.click();
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
}); });
@ -147,30 +195,46 @@ describe('Emulation', () => {
itFailsFirefox('should work', async () => { itFailsFirefox('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( expect(
true await page.evaluate(() => {
); return matchMedia('screen').matches;
expect(await page.evaluate(() => matchMedia('print').matches)).toBe( })
false ).toBe(true);
); expect(
await page.evaluate(() => {
return matchMedia('print').matches;
})
).toBe(false);
await page.emulateMediaType('print'); await page.emulateMediaType('print');
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( expect(
false await page.evaluate(() => {
); return matchMedia('screen').matches;
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); })
await page.emulateMediaType(null); ).toBe(false);
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( expect(
true await page.evaluate(() => {
); return matchMedia('print').matches;
expect(await page.evaluate(() => matchMedia('print').matches)).toBe( })
false ).toBe(true);
); await page.emulateMediaType();
expect(
await page.evaluate(() => {
return matchMedia('screen').matches;
})
).toBe(true);
expect(
await page.evaluate(() => {
return matchMedia('print').matches;
})
).toBe(false);
}); });
it('should throw in case of bad argument', async () => { it('should throw in case of bad argument', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page.emulateMediaType('bad').catch((error_) => (error = error_)); await page.emulateMediaType('bad').catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Unsupported media type: bad'); expect(error.message).toBe('Unsupported media type: bad');
}); });
}); });
@ -183,105 +247,125 @@ describe('Emulation', () => {
{ name: 'prefers-reduced-motion', value: 'reduce' }, { name: 'prefers-reduced-motion', value: 'reduce' },
]); ]);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-reduced-motion: reduce)').matches return matchMedia('(prefers-reduced-motion: reduce)').matches;
) })
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-reduced-motion: no-preference)').matches return matchMedia('(prefers-reduced-motion: no-preference)').matches;
) })
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([ await page.emulateMediaFeatures([
{ name: 'prefers-color-scheme', value: 'light' }, { name: 'prefers-color-scheme', value: 'light' },
]); ]);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: light)').matches return matchMedia('(prefers-color-scheme: light)').matches;
) })
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: dark)').matches return matchMedia('(prefers-color-scheme: dark)').matches;
) })
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([ await page.emulateMediaFeatures([
{ name: 'prefers-color-scheme', value: 'dark' }, { name: 'prefers-color-scheme', value: 'dark' },
]); ]);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: dark)').matches return matchMedia('(prefers-color-scheme: dark)').matches;
) })
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: light)').matches return matchMedia('(prefers-color-scheme: light)').matches;
) })
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([ await page.emulateMediaFeatures([
{ name: 'prefers-reduced-motion', value: 'reduce' }, { name: 'prefers-reduced-motion', value: 'reduce' },
{ name: 'prefers-color-scheme', value: 'light' }, { name: 'prefers-color-scheme', value: 'light' },
]); ]);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-reduced-motion: reduce)').matches return matchMedia('(prefers-reduced-motion: reduce)').matches;
) })
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-reduced-motion: no-preference)').matches return matchMedia('(prefers-reduced-motion: no-preference)').matches;
) })
).toBe(false); ).toBe(false);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: light)').matches return matchMedia('(prefers-color-scheme: light)').matches;
) })
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate( await page.evaluate(() => {
() => matchMedia('(prefers-color-scheme: dark)').matches return matchMedia('(prefers-color-scheme: dark)').matches;
) })
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'srgb' }]); await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'srgb' }]);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: p3)').matches;
})
).toBe(false); ).toBe(false);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: srgb)').matches;
})
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: rec2020)').matches;
})
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'p3' }]); await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'p3' }]);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: p3)').matches;
})
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: srgb)').matches;
})
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: rec2020)').matches;
})
).toBe(false); ).toBe(false);
await page.emulateMediaFeatures([ await page.emulateMediaFeatures([
{ name: 'color-gamut', value: 'rec2020' }, { name: 'color-gamut', value: 'rec2020' },
]); ]);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: p3)').matches;
})
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: srgb)').matches;
})
).toBe(true); ).toBe(true);
expect( expect(
await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) await page.evaluate(() => {
return matchMedia('(color-gamut: rec2020)').matches;
})
).toBe(true); ).toBe(true);
}); });
it('should throw in case of bad argument', async () => { it('should throw in case of bad argument', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.emulateMediaFeatures([{ name: 'bad', value: '' }]) .emulateMediaFeatures([{ name: 'bad', value: '' }])
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Unsupported media feature: bad'); expect(error.message).toBe('Unsupported media feature: bad');
}); });
}); });
@ -291,25 +375,37 @@ describe('Emulation', () => {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluate(() => { await page.evaluate(() => {
globalThis.date = new Date(1479579154987); (globalThis as any).date = new Date(1479579154987);
}); });
await page.emulateTimezone('America/Jamaica'); await page.emulateTimezone('America/Jamaica');
expect(await page.evaluate(() => globalThis.date.toString())).toBe( expect(
'Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)' await page.evaluate(() => {
); return (globalThis as any).date.toString();
})
).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)');
await page.emulateTimezone('Pacific/Honolulu'); await page.emulateTimezone('Pacific/Honolulu');
expect(await page.evaluate(() => globalThis.date.toString())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).date.toString();
})
).toBe(
'Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)' 'Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'
); );
await page.emulateTimezone('America/Buenos_Aires'); await page.emulateTimezone('America/Buenos_Aires');
expect(await page.evaluate(() => globalThis.date.toString())).toBe( expect(
'Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)' await page.evaluate(() => {
); return (globalThis as any).date.toString();
})
).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)');
await page.emulateTimezone('Europe/Berlin'); await page.emulateTimezone('Europe/Berlin');
expect(await page.evaluate(() => globalThis.date.toString())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).date.toString();
})
).toBe(
'Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)' 'Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'
); );
}); });
@ -317,10 +413,14 @@ describe('Emulation', () => {
it('should throw for invalid timezone IDs', async () => { it('should throw for invalid timezone IDs', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page.emulateTimezone('Foo/Bar').catch((error_) => (error = error_)); await page.emulateTimezone('Foo/Bar').catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Invalid timezone ID: Foo/Bar'); expect(error.message).toBe('Invalid timezone ID: Foo/Bar');
await page.emulateTimezone('Baz/Qux').catch((error_) => (error = error_)); await page.emulateTimezone('Baz/Qux').catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Invalid timezone ID: Baz/Qux'); expect(error.message).toBe('Invalid timezone ID: Baz/Qux');
}); });
}); });
@ -378,11 +478,13 @@ describe('Emulation', () => {
it('should throw for invalid vision deficiencies', async () => { it('should throw for invalid vision deficiencies', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
// @ts-expect-error deliberately passign invalid deficiency // @ts-expect-error deliberately passign invalid deficiency
.emulateVisionDeficiency('invalid') .emulateVisionDeficiency('invalid')
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Unsupported vision deficiency: invalid'); expect(error.message).toBe('Unsupported vision deficiency: invalid');
}); });
}); });
@ -391,8 +493,8 @@ describe('Emulation', () => {
it('should change navigator.connection.effectiveType', async () => { it('should change navigator.connection.effectiveType', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
const slow3G = puppeteer.networkConditions['Slow 3G']; const slow3G = puppeteer.networkConditions['Slow 3G']!;
const fast3G = puppeteer.networkConditions['Fast 3G']; const fast3G = puppeteer.networkConditions['Fast 3G']!;
expect( expect(
await page.evaluate('window.navigator.connection.effectiveType') await page.evaluate('window.navigator.connection.effectiveType')

View File

@ -22,7 +22,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
const bigint = typeof BigInt !== 'undefined'; const bigint = typeof BigInt !== 'undefined';
@ -34,55 +34,79 @@ describe('Evaluation specs', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => 7 * 3); const result = await page.evaluate(() => {
return 7 * 3;
});
expect(result).toBe(21); expect(result).toBe(21);
}); });
(bigint ? it : xit)('should transfer BigInt', async () => { (bigint ? it : xit)('should transfer BigInt', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a: bigint) => a, BigInt(42)); const result = await page.evaluate((a: bigint) => {
return a;
}, BigInt(42));
expect(result).toBe(BigInt(42)); expect(result).toBe(BigInt(42));
}); });
it('should transfer NaN', async () => { it('should transfer NaN', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, NaN); const result = await page.evaluate((a) => {
return a;
}, NaN);
expect(Object.is(result, NaN)).toBe(true); expect(Object.is(result, NaN)).toBe(true);
}); });
it('should transfer -0', async () => { it('should transfer -0', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, -0); const result = await page.evaluate((a) => {
return a;
}, -0);
expect(Object.is(result, -0)).toBe(true); expect(Object.is(result, -0)).toBe(true);
}); });
it('should transfer Infinity', async () => { it('should transfer Infinity', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, Infinity); const result = await page.evaluate((a) => {
return a;
}, Infinity);
expect(Object.is(result, Infinity)).toBe(true); expect(Object.is(result, Infinity)).toBe(true);
}); });
it('should transfer -Infinity', async () => { it('should transfer -Infinity', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, -Infinity); const result = await page.evaluate((a) => {
return a;
}, -Infinity);
expect(Object.is(result, -Infinity)).toBe(true); expect(Object.is(result, -Infinity)).toBe(true);
}); });
it('should transfer arrays', async () => { it('should transfer arrays', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, [1, 2, 3]); const result = await page.evaluate(
(a) => {
return a;
},
[1, 2, 3]
);
expect(result).toEqual([1, 2, 3]); expect(result).toEqual([1, 2, 3]);
}); });
it('should transfer arrays as arrays, not objects', async () => { it('should transfer arrays as arrays, not objects', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => Array.isArray(a), [1, 2, 3]); const result = await page.evaluate(
(a) => {
return Array.isArray(a);
},
[1, 2, 3]
);
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('should modify global environment', async () => { it('should modify global environment', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluate(() => (globalThis.globalVar = 123)); await page.evaluate(() => {
return ((globalThis as any).globalVar = 123);
});
expect(await page.evaluate('globalVar')).toBe(123); expect(await page.evaluate('globalVar')).toBe(123);
}); });
it('should evaluate in the page context', async () => { it('should evaluate in the page context', async () => {
@ -96,18 +120,22 @@ describe('Evaluation specs', function () {
async () => { async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); expect(
await page.evaluate(() => {
return [Symbol('foo4')];
})
).toBe(undefined);
} }
); );
it('should work with function shorthands', async () => { it('should work with function shorthands', async () => {
const { page } = getTestState(); const { page } = getTestState();
const a = { const a = {
sum(a, b) { sum(a: number, b: number) {
return a + b; return a + b;
}, },
async mult(a, b) { async mult(a: number, b: number) {
return a * b; return a * b;
}, },
}; };
@ -117,27 +145,36 @@ describe('Evaluation specs', function () {
it('should work with unicode chars', async () => { it('should work with unicode chars', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a['中文字符'], { const result = await page.evaluate(
(a) => {
return a['中文字符'];
},
{
中文字符: 42, 中文字符: 42,
}); }
);
expect(result).toBe(42); expect(result).toBe(42);
}); });
itFailsFirefox('should throw when evaluation triggers reload', async () => { itFailsFirefox('should throw when evaluation triggers reload', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.evaluate(() => { .evaluate(() => {
location.reload(); location.reload();
return new Promise(() => {}); return new Promise(() => {});
}) })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Protocol error'); expect(error.message).toContain('Protocol error');
}); });
it('should await promise', async () => { it('should await promise', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => Promise.resolve(8 * 7)); const result = await page.evaluate(() => {
return Promise.resolve(8 * 7);
});
expect(result).toBe(56); expect(result).toBe(56);
}); });
it('should work right after framenavigated', async () => { it('should work right after framenavigated', async () => {
@ -145,7 +182,9 @@ describe('Evaluation specs', function () {
let frameEvaluation = null; let frameEvaluation = null;
page.on('framenavigated', async (frame) => { page.on('framenavigated', async (frame) => {
frameEvaluation = frame.evaluate(() => 6 * 7); frameEvaluation = frame.evaluate(() => {
return 6 * 7;
});
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(await frameEvaluation).toBe(42); expect(await frameEvaluation).toBe(42);
@ -154,50 +193,63 @@ describe('Evaluation specs', function () {
const { page } = getTestState(); const { page } = getTestState();
// Setup inpage callback, which calls Page.evaluate // Setup inpage callback, which calls Page.evaluate
await page.exposeFunction('callController', async function (a, b) { await page.exposeFunction(
return await page.evaluate<(a: number, b: number) => number>( 'callController',
(a, b) => a * b, async function (a: number, b: number) {
return await page.evaluate(
(a: number, b: number): number => {
return a * b;
},
a, a,
b b
); );
}); }
);
const result = await page.evaluate(async function () { const result = await page.evaluate(async function () {
return await globalThis.callController(9, 3); return await (globalThis as any).callController(9, 3);
}); });
expect(result).toBe(27); expect(result).toBe(27);
}); });
it('should reject promise with exception', async () => { it('should reject promise with exception', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.evaluate(() => {
// @ts-expect-error we know the object doesn't exist // @ts-expect-error we know the object doesn't exist
.evaluate(() => notExistingObject.property) return notExistingObject.property;
.catch((error_) => (error = error_)); })
.catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('notExistingObject'); expect(error.message).toContain('notExistingObject');
}); });
it('should support thrown strings as error messages', async () => { it('should support thrown strings as error messages', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.evaluate(() => { .evaluate(() => {
throw 'qwerty'; throw 'qwerty';
}) })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('qwerty'); expect(error.message).toContain('qwerty');
}); });
it('should support thrown numbers as error messages', async () => { it('should support thrown numbers as error messages', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.evaluate(() => { .evaluate(() => {
throw 100500; throw 100500;
}) })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('100500'); expect(error.message).toContain('100500');
}); });
@ -205,46 +257,60 @@ describe('Evaluation specs', function () {
const { page } = getTestState(); const { page } = getTestState();
const object = { foo: 'bar!' }; const object = { foo: 'bar!' };
const result = await page.evaluate((a) => a, object); const result = await page.evaluate((a) => {
return a;
}, object);
expect(result).not.toBe(object); expect(result).not.toBe(object);
expect(result).toEqual(object); expect(result).toEqual(object);
}); });
(bigint ? it : xit)('should return BigInt', async () => { (bigint ? it : xit)('should return BigInt', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => BigInt(42)); const result = await page.evaluate(() => {
return BigInt(42);
});
expect(result).toBe(BigInt(42)); expect(result).toBe(BigInt(42));
}); });
it('should return NaN', async () => { it('should return NaN', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => NaN); const result = await page.evaluate(() => {
return NaN;
});
expect(Object.is(result, NaN)).toBe(true); expect(Object.is(result, NaN)).toBe(true);
}); });
it('should return -0', async () => { it('should return -0', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => -0); const result = await page.evaluate(() => {
return -0;
});
expect(Object.is(result, -0)).toBe(true); expect(Object.is(result, -0)).toBe(true);
}); });
it('should return Infinity', async () => { it('should return Infinity', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => Infinity); const result = await page.evaluate(() => {
return Infinity;
});
expect(Object.is(result, Infinity)).toBe(true); expect(Object.is(result, Infinity)).toBe(true);
}); });
it('should return -Infinity', async () => { it('should return -Infinity', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate(() => -Infinity); const result = await page.evaluate(() => {
return -Infinity;
});
expect(Object.is(result, -Infinity)).toBe(true); expect(Object.is(result, -Infinity)).toBe(true);
}); });
it('should accept "undefined" as one of multiple parameters', async () => { it('should accept "null" as one of multiple parameters', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate( const result = await page.evaluate(
(a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), (a, b) => {
undefined, return Object.is(a, null) && Object.is(b, 'foo');
},
null,
'foo' 'foo'
); );
expect(result).toBe(true); expect(result).toBe(true);
@ -252,14 +318,22 @@ describe('Evaluation specs', function () {
it('should properly serialize null fields', async () => { it('should properly serialize null fields', async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await page.evaluate(() => ({ a: undefined }))).toEqual({}); expect(
await page.evaluate(() => {
return { a: undefined };
})
).toEqual({});
}); });
itFailsFirefox( itFailsFirefox(
'should return undefined for non-serializable objects', 'should return undefined for non-serializable objects',
async () => { async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await page.evaluate(() => window)).toBe(undefined); expect(
await page.evaluate(() => {
return window;
})
).toBe(undefined);
} }
); );
itFailsFirefox('should fail for circular object', async () => { itFailsFirefox('should fail for circular object', async () => {
@ -268,7 +342,7 @@ describe('Evaluation specs', function () {
const result = await page.evaluate(() => { const result = await page.evaluate(() => {
const a: { [x: string]: any } = {}; const a: { [x: string]: any } = {};
const b = { a }; const b = { a };
a.b = b; a['b'] = b;
return a; return a;
}); });
expect(result).toBe(undefined); expect(result).toBe(undefined);
@ -276,15 +350,21 @@ describe('Evaluation specs', function () {
itFailsFirefox('should be able to throw a tricky error', async () => { itFailsFirefox('should be able to throw a tricky error', async () => {
const { page } = getTestState(); const { page } = getTestState();
const windowHandle = await page.evaluateHandle(() => window); const windowHandle = await page.evaluateHandle(() => {
return window;
});
const errorText = await windowHandle const errorText = await windowHandle
.jsonValue<string>() .jsonValue<string>()
.catch((error_) => error_.message); .catch((error_) => {
return error_.message;
});
const error = await page const error = await page
.evaluate<(errorText: string) => Error>((errorText) => { .evaluate<(errorText: string) => Error>((errorText) => {
throw new Error(errorText); throw new Error(errorText);
}, errorText) }, errorText)
.catch((error_) => error_); .catch((error_) => {
return error_;
});
expect(error.message).toContain(errorText); expect(error.message).toContain(errorText);
}); });
it('should accept a string', async () => { it('should accept a string', async () => {
@ -309,24 +389,27 @@ describe('Evaluation specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<section>42</section>'); await page.setContent('<section>42</section>');
const element = await page.$('section'); const element = (await page.$('section'))!;
const text = await page.evaluate<(e: HTMLElement) => string>( const text = await page.evaluate((e) => {
(e) => e.textContent, return e.textContent;
element }, element);
);
expect(text).toBe('42'); expect(text).toBe('42');
}); });
it('should throw if underlying element was disposed', async () => { it('should throw if underlying element was disposed', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<section>39</section>'); await page.setContent('<section>39</section>');
const element = await page.$('section'); const element = (await page.$('section'))!;
expect(element).toBeTruthy(); expect(element).toBeTruthy();
await element.dispose(); await element.dispose();
let error = null; let error!: Error;
await page await page
.evaluate((e: HTMLElement) => e.textContent, element) .evaluate((e: HTMLElement) => {
.catch((error_) => (error = error_)); return e.textContent;
}, element)
.catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('JSHandle is disposed'); expect(error.message).toContain('JSHandle is disposed');
}); });
itFailsFirefox( itFailsFirefox(
@ -335,11 +418,15 @@ describe('Evaluation specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const bodyHandle = await page.frames()[1].$('body'); const bodyHandle = await page.frames()[1]!.$('body');
let error = null; let error!: Error;
await page await page
.evaluate((body: HTMLElement) => body.innerHTML, bodyHandle) .evaluate((body: HTMLElement) => {
.catch((error_) => (error = error_)); return body.innerHTML;
}, bodyHandle)
.catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain( expect(error.message).toContain(
'JSHandles can be evaluated only in the context they were created' 'JSHandles can be evaluated only in the context they were created'
@ -363,11 +450,17 @@ describe('Evaluation specs', function () {
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
executionContext.evaluate(() => window.location.reload()), executionContext.evaluate(() => {
return window.location.reload();
}),
]); ]);
const error = await executionContext const error = await executionContext
.evaluate(() => null) .evaluate(() => {
.catch((error_) => error_); return null;
})
.catch((error_) => {
return error_;
});
expect((error as Error).message).toContain('navigation'); expect((error as Error).message).toContain('navigation');
}); });
itFailsFirefox( itFailsFirefox(
@ -386,23 +479,24 @@ describe('Evaluation specs', function () {
it('should transfer 100Mb of data from page to node.js', async function () { it('should transfer 100Mb of data from page to node.js', async function () {
const { page } = getTestState(); const { page } = getTestState();
const a = await page.evaluate<() => string>(() => const a = await page.evaluate<() => string>(() => {
Array(100 * 1024 * 1024 + 1).join('a') return Array(100 * 1024 * 1024 + 1).join('a');
); });
expect(a.length).toBe(100 * 1024 * 1024); expect(a.length).toBe(100 * 1024 * 1024);
}); });
it('should throw error with detailed information on exception inside promise ', async () => { it('should throw error with detailed information on exception inside promise ', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.evaluate( .evaluate(() => {
() => return new Promise(() => {
new Promise(() => {
throw new Error('Error in promise'); throw new Error('Error in promise');
});
}) })
) .catch((error_) => {
.catch((error_) => (error = error_)); return (error = error_);
});
expect(error.message).toContain('Error in promise'); expect(error.message).toContain('Error in promise');
}); });
}); });
@ -412,26 +506,38 @@ describe('Evaluation specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.evaluateOnNewDocument(function () { await page.evaluateOnNewDocument(function () {
globalThis.injected = 123; (globalThis as any).injected = 123;
}); });
await page.goto(server.PREFIX + '/tamperable.html'); await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => globalThis.result)).toBe(123); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe(123);
}); });
it('should work with CSP', async () => { it('should work with CSP', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setCSP('/empty.html', 'script-src ' + server.PREFIX); server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.evaluateOnNewDocument(function () { await page.evaluateOnNewDocument(function () {
globalThis.injected = 123; (globalThis as any).injected = 123;
}); });
await page.goto(server.PREFIX + '/empty.html'); await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => globalThis.injected)).toBe(123); expect(
await page.evaluate(() => {
return (globalThis as any).injected;
})
).toBe(123);
// Make sure CSP works. // Make sure CSP works.
await page await page.addScriptTag({ content: 'window.e = 10;' }).catch((error) => {
.addScriptTag({ content: 'window.e = 10;' }) return void error;
.catch((error) => void error); });
expect(await page.evaluate(() => (window as any).e)).toBe(undefined); expect(
await page.evaluate(() => {
return (window as any).e;
})
).toBe(undefined);
}); });
}); });
@ -442,10 +548,22 @@ describe('Evaluation specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
await page.frames()[0].evaluate(() => (globalThis.FOO = 'foo')); await page.frames()[0]!.evaluate(() => {
await page.frames()[1].evaluate(() => (globalThis.FOO = 'bar')); return ((globalThis as any).FOO = 'foo');
expect(await page.frames()[0].evaluate(() => globalThis.FOO)).toBe('foo'); });
expect(await page.frames()[1].evaluate(() => globalThis.FOO)).toBe('bar'); await page.frames()[1]!.evaluate(() => {
return ((globalThis as any).FOO = 'bar');
});
expect(
await page.frames()[0]!.evaluate(() => {
return (globalThis as any).FOO;
})
).toBe('foo');
expect(
await page.frames()[1]!.evaluate(() => {
return (globalThis as any).FOO;
})
).toBe('bar');
}); });
it('should have correct execution contexts', async () => { it('should have correct execution contexts', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -453,10 +571,14 @@ describe('Evaluation specs', function () {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect( expect(
await page.frames()[0].evaluate(() => document.body.textContent.trim()) await page.frames()[0]!.evaluate(() => {
return document.body.textContent!.trim();
})
).toBe(''); ).toBe('');
expect( expect(
await page.frames()[1].evaluate(() => document.body.textContent.trim()) await page.frames()[1]!.evaluate(() => {
return document.body.textContent!.trim();
})
).toBe(`Hi, I'm frame`); ).toBe(`Hi, I'm frame`);
}); });
it('should execute after cross-site navigation', async () => { it('should execute after cross-site navigation', async () => {
@ -464,13 +586,17 @@ describe('Evaluation specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame(); const mainFrame = page.mainFrame();
expect(await mainFrame.evaluate(() => window.location.href)).toContain( expect(
'localhost' await mainFrame.evaluate(() => {
); return window.location.href;
})
).toContain('localhost');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(await mainFrame.evaluate(() => window.location.href)).toContain( expect(
'127' await mainFrame.evaluate(() => {
); return window.location.href;
})
).toContain('127');
}); });
}); });
}); });

View File

@ -17,7 +17,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import expect from 'expect'; import expect from 'expect';
import { getTestState, itHeadlessOnly } from './mocha-utils'; // eslint-disable-line import/extensions import { getTestState, itHeadlessOnly } from './mocha-utils.js';
import path from 'path'; import path from 'path';
@ -36,8 +36,12 @@ describe('Fixtures', function () {
puppeteerPath, puppeteerPath,
JSON.stringify(options), JSON.stringify(options),
]); ]);
res.stderr.on('data', (data) => (dumpioData += data.toString('utf8'))); res.stderr.on('data', (data) => {
await new Promise((resolve) => res.on('close', resolve)); return (dumpioData += data.toString('utf8'));
});
await new Promise((resolve) => {
return res.on('close', resolve);
});
expect(dumpioData).toContain('message from dumpio'); expect(dumpioData).toContain('message from dumpio');
}); });
it('should dump browser process stderr', async () => { it('should dump browser process stderr', async () => {
@ -51,8 +55,12 @@ describe('Fixtures', function () {
puppeteerPath, puppeteerPath,
JSON.stringify(options), JSON.stringify(options),
]); ]);
res.stderr.on('data', (data) => (dumpioData += data.toString('utf8'))); res.stderr.on('data', (data) => {
await new Promise((resolve) => res.on('close', resolve)); return (dumpioData += data.toString('utf8'));
});
await new Promise((resolve) => {
return res.on('close', resolve);
});
expect(dumpioData).toContain('DevTools listening on ws://'); expect(dumpioData).toContain('DevTools listening on ws://');
}); });
it('should close the browser when the node process closes', async () => { it('should close the browser when the node process closes', async () => {
@ -68,10 +76,10 @@ describe('Fixtures', function () {
puppeteerPath, puppeteerPath,
JSON.stringify(options), JSON.stringify(options),
]); ]);
let wsEndPointCallback; let wsEndPointCallback: (value: string) => void;
const wsEndPointPromise = new Promise<string>( const wsEndPointPromise = new Promise<string>((x) => {
(x) => (wsEndPointCallback = x) return (wsEndPointCallback = x);
); });
let output = ''; let output = '';
res.stdout.on('data', (data) => { res.stdout.on('data', (data) => {
output += data; output += data;
@ -83,13 +91,17 @@ describe('Fixtures', function () {
browserWSEndpoint: await wsEndPointPromise, browserWSEndpoint: await wsEndPointPromise,
}); });
const promises = [ const promises = [
new Promise((resolve) => browser.once('disconnected', resolve)), new Promise((resolve) => {
new Promise((resolve) => res.on('close', resolve)), return browser.once('disconnected', resolve);
}),
new Promise((resolve) => {
return res.on('close', resolve);
}),
]; ];
if (process.platform === 'win32') { if (process.platform === 'win32') {
execSync(`taskkill /pid ${res.pid} /T /F`); execSync(`taskkill /pid ${res.pid} /T /F`);
} else { } else {
process.kill(res.pid); process.kill(res.pid!);
} }
await Promise.all(promises); await Promise.all(promises);
}); });

View File

@ -14,15 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
import utils from './utils.js';
import expect from 'expect'; import expect from 'expect';
import { CDPSession } from '../../lib/cjs/puppeteer/common/Connection.js';
import { Frame } from '../../lib/cjs/puppeteer/common/FrameManager.js';
import { import {
getTestState, getTestState,
itFailsFirefox,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, } from './mocha-utils.js';
} from './mocha-utils'; // eslint-disable-line import/extensions import utils, { dumpFrames } from './utils.js';
import { CDPSession } from '../../lib/cjs/puppeteer/common/Connection.js';
describe('Frame specs', function () { describe('Frame specs', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -36,8 +37,8 @@ describe('Frame specs', function () {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
const [frame1, frame2] = page.frames(); const [frame1, frame2] = page.frames();
const context1 = await frame1.executionContext(); const context1 = await frame1!.executionContext();
const context2 = await frame2.executionContext(); const context2 = await frame2!.executionContext();
expect(context1).toBeTruthy(); expect(context1).toBeTruthy();
expect(context2).toBeTruthy(); expect(context2).toBeTruthy();
expect(context1 !== context2).toBeTruthy(); expect(context1 !== context2).toBeTruthy();
@ -45,12 +46,20 @@ describe('Frame specs', function () {
expect(context2.frame()).toBe(frame2); expect(context2.frame()).toBe(frame2);
await Promise.all([ await Promise.all([
context1.evaluate(() => (globalThis.a = 1)), context1.evaluate(() => {
context2.evaluate(() => (globalThis.a = 2)), return ((globalThis as any).a = 1);
}),
context2.evaluate(() => {
return ((globalThis as any).a = 2);
}),
]); ]);
const [a1, a2] = await Promise.all([ const [a1, a2] = await Promise.all([
context1.evaluate(() => globalThis.a), context1.evaluate(() => {
context2.evaluate(() => globalThis.a), return (globalThis as any).a;
}),
context2.evaluate(() => {
return (globalThis as any).a;
}),
]); ]);
expect(a1).toBe(1); expect(a1).toBe(1);
expect(a2).toBe(2); expect(a2).toBe(2);
@ -63,7 +72,9 @@ describe('Frame specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame(); const mainFrame = page.mainFrame();
const windowHandle = await mainFrame.evaluateHandle(() => window); const windowHandle = await mainFrame.evaluateHandle(() => {
return window;
});
expect(windowHandle).toBeTruthy(); expect(windowHandle).toBeTruthy();
}); });
}); });
@ -72,10 +83,20 @@ describe('Frame specs', function () {
itFailsFirefox('should throw for detached frames', async () => { itFailsFirefox('should throw for detached frames', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame1 = (await utils.attachFrame(
page,
'frame1',
server.EMPTY_PAGE
))!;
await utils.detachFrame(page, 'frame1'); await utils.detachFrame(page, 'frame1');
let error = null; let error!: Error;
await frame1.evaluate(() => 7 * 8).catch((error_) => (error = error_)); await frame1
.evaluate(() => {
return 7 * 8;
})
.catch((error_) => {
return (error = error_);
});
expect(error.message).toContain( expect(error.message).toContain(
'Execution context is not available in detached frame' 'Execution context is not available in detached frame'
); );
@ -89,7 +110,9 @@ describe('Frame specs', function () {
// This test checks if Frame.evaluate allows a readonly array to be an argument. // This test checks if Frame.evaluate allows a readonly array to be an argument.
// See https://github.com/puppeteer/puppeteer/issues/6953. // See https://github.com/puppeteer/puppeteer/issues/6953.
const readonlyArray: readonly string[] = ['a', 'b', 'c']; const readonlyArray: readonly string[] = ['a', 'b', 'c'];
await mainFrame.evaluate((arr) => arr, readonlyArray); await mainFrame.evaluate((arr) => {
return arr;
}, readonlyArray);
}); });
}); });
@ -98,7 +121,7 @@ describe('Frame specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/nested-frames.html'); await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(utils.dumpFrames(page.mainFrame())).toEqual([ expect(dumpFrames(page.mainFrame())).toEqual([
'http://localhost:<PORT>/frames/nested-frames.html', 'http://localhost:<PORT>/frames/nested-frames.html',
' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/two-frames.html (2frames)',
' http://localhost:<PORT>/frames/frame.html (uno)', ' http://localhost:<PORT>/frames/frame.html (uno)',
@ -113,25 +136,31 @@ describe('Frame specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
// validate frameattached events // validate frameattached events
const attachedFrames = []; const attachedFrames: Frame[] = [];
page.on('frameattached', (frame) => attachedFrames.push(frame)); page.on('frameattached', (frame) => {
return attachedFrames.push(frame);
});
await utils.attachFrame(page, 'frame1', './assets/frame.html'); await utils.attachFrame(page, 'frame1', './assets/frame.html');
expect(attachedFrames.length).toBe(1); expect(attachedFrames.length).toBe(1);
expect(attachedFrames[0].url()).toContain('/assets/frame.html'); expect(attachedFrames[0]!.url()).toContain('/assets/frame.html');
// validate framenavigated events // validate framenavigated events
const navigatedFrames = []; const navigatedFrames: Frame[] = [];
page.on('framenavigated', (frame) => navigatedFrames.push(frame)); page.on('framenavigated', (frame) => {
return navigatedFrames.push(frame);
});
await utils.navigateFrame(page, 'frame1', './empty.html'); await utils.navigateFrame(page, 'frame1', './empty.html');
expect(navigatedFrames.length).toBe(1); expect(navigatedFrames.length).toBe(1);
expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); expect(navigatedFrames[0]!.url()).toBe(server.EMPTY_PAGE);
// validate framedetached events // validate framedetached events
const detachedFrames = []; const detachedFrames: Frame[] = [];
page.on('framedetached', (frame) => detachedFrames.push(frame)); page.on('framedetached', (frame) => {
return detachedFrames.push(frame);
});
await utils.detachFrame(page, 'frame1'); await utils.detachFrame(page, 'frame1');
expect(detachedFrames.length).toBe(1); expect(detachedFrames.length).toBe(1);
expect(detachedFrames[0].isDetached()).toBe(true); expect(detachedFrames[0]!.isDetached()).toBe(true);
} }
); );
it('should send "framenavigated" when navigating on anchor URLs', async () => { it('should send "framenavigated" when navigating on anchor URLs', async () => {
@ -156,8 +185,12 @@ describe('Frame specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
let hasEvents = false; let hasEvents = false;
page.on('frameattached', () => (hasEvents = true)); page.on('frameattached', () => {
page.on('framedetached', () => (hasEvents = true)); return (hasEvents = true);
});
page.on('framedetached', () => {
return (hasEvents = true);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(hasEvents).toBe(false); expect(hasEvents).toBe(false);
}); });
@ -167,9 +200,15 @@ describe('Frame specs', function () {
let attachedFrames = []; let attachedFrames = [];
let detachedFrames = []; let detachedFrames = [];
let navigatedFrames = []; let navigatedFrames = [];
page.on('frameattached', (frame) => attachedFrames.push(frame)); page.on('frameattached', (frame) => {
page.on('framedetached', (frame) => detachedFrames.push(frame)); return attachedFrames.push(frame);
page.on('framenavigated', (frame) => navigatedFrames.push(frame)); });
page.on('framedetached', (frame) => {
return detachedFrames.push(frame);
});
page.on('framenavigated', (frame) => {
return navigatedFrames.push(frame);
});
await page.goto(server.PREFIX + '/frames/nested-frames.html'); await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(attachedFrames.length).toBe(4); expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0); expect(detachedFrames.length).toBe(0);
@ -189,9 +228,15 @@ describe('Frame specs', function () {
let attachedFrames = []; let attachedFrames = [];
let detachedFrames = []; let detachedFrames = [];
let navigatedFrames = []; let navigatedFrames = [];
page.on('frameattached', (frame) => attachedFrames.push(frame)); page.on('frameattached', (frame) => {
page.on('framedetached', (frame) => detachedFrames.push(frame)); return attachedFrames.push(frame);
page.on('framenavigated', (frame) => navigatedFrames.push(frame)); });
page.on('framedetached', (frame) => {
return detachedFrames.push(frame);
});
page.on('framenavigated', (frame) => {
return navigatedFrames.push(frame);
});
await page.goto(server.PREFIX + '/frames/frameset.html'); await page.goto(server.PREFIX + '/frames/frameset.html');
expect(attachedFrames.length).toBe(4); expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0); expect(detachedFrames.length).toBe(0);
@ -212,11 +257,13 @@ describe('Frame specs', function () {
await page.evaluate(async (url: string) => { await page.evaluate(async (url: string) => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');
frame.src = url; frame.src = url;
document.body.shadowRoot.appendChild(frame); document.body.shadowRoot!.appendChild(frame);
await new Promise((x) => (frame.onload = x)); await new Promise((x) => {
return (frame.onload = x);
});
}, server.EMPTY_PAGE); }, server.EMPTY_PAGE);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE); expect(page.frames()[1]!.url()).toBe(server.EMPTY_PAGE);
}); });
itFailsFirefox('should report frame.name()', async () => { itFailsFirefox('should report frame.name()', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -227,20 +274,22 @@ describe('Frame specs', function () {
frame.name = 'theFrameName'; frame.name = 'theFrameName';
frame.src = url; frame.src = url;
document.body.appendChild(frame); document.body.appendChild(frame);
return new Promise((x) => (frame.onload = x)); return new Promise((x) => {
return (frame.onload = x);
});
}, server.EMPTY_PAGE); }, server.EMPTY_PAGE);
expect(page.frames()[0].name()).toBe(''); expect(page.frames()[0]!.name()).toBe('');
expect(page.frames()[1].name()).toBe('theFrameId'); expect(page.frames()[1]!.name()).toBe('theFrameId');
expect(page.frames()[2].name()).toBe('theFrameName'); expect(page.frames()[2]!.name()).toBe('theFrameName');
}); });
itFailsFirefox('should report frame.parent()', async () => { itFailsFirefox('should report frame.parent()', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
expect(page.frames()[0].parentFrame()).toBe(null); expect(page.frames()[0]!.parentFrame()).toBe(null);
expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); expect(page.frames()[1]!.parentFrame()).toBe(page.mainFrame());
expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); expect(page.frames()[2]!.parentFrame()).toBe(page.mainFrame());
}); });
itFailsFirefox( itFailsFirefox(
'should report different frame instance when frame re-attaches', 'should report different frame instance when frame re-attaches',
@ -253,13 +302,15 @@ describe('Frame specs', function () {
server.EMPTY_PAGE server.EMPTY_PAGE
); );
await page.evaluate(() => { await page.evaluate(() => {
globalThis.frame = document.querySelector('#frame1'); (globalThis as any).frame = document.querySelector('#frame1');
globalThis.frame.remove(); (globalThis as any).frame.remove();
}); });
expect(frame1.isDetached()).toBe(true); expect(frame1!.isDetached()).toBe(true);
const [frame2] = await Promise.all([ const [frame2] = await Promise.all([
utils.waitEvent(page, 'frameattached'), utils.waitEvent(page, 'frameattached'),
page.evaluate(() => document.body.appendChild(globalThis.frame)), page.evaluate(() => {
return document.body.appendChild((globalThis as any).frame);
}),
]); ]);
expect(frame2.isDetached()).toBe(false); expect(frame2.isDetached()).toBe(false);
expect(frame1).not.toBe(frame2); expect(frame1).not.toBe(frame2);
@ -271,7 +322,7 @@ describe('Frame specs', function () {
await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html'); await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(page.frames()[1].url()).toBe( expect(page.frames()[1]!.url()).toBe(
server.PREFIX + '/frames/frame.html?param=value#fragment' server.PREFIX + '/frames/frame.html?param=value#fragment'
); );
}); });
@ -281,11 +332,11 @@ describe('Frame specs', function () {
await page.setViewport({ width: 1000, height: 1000 }); await page.setViewport({ width: 1000, height: 1000 });
await page.goto(server.PREFIX + '/frames/lazy-frame.html'); await page.goto(server.PREFIX + '/frames/lazy-frame.html');
expect(page.frames().map((frame) => frame._hasStartedLoading)).toEqual([ expect(
true, page.frames().map((frame) => {
true, return frame._hasStartedLoading;
false, })
]); ).toEqual([true, true, false]);
}); });
}); });

View File

@ -1,4 +1,3 @@
// @ts-nocheck
/** /**
* Copyright 2017 Google Inc. All rights reserved. * Copyright 2017 Google Inc. All rights reserved.
* *
@ -14,45 +13,61 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const path = require('path'); import assert from 'assert';
const fs = require('fs'); import diff from 'diff';
const Diff = require('text-diff'); import fs from 'fs';
const mime = require('mime'); import jpeg from 'jpeg-js';
const PNG = require('pngjs').PNG; import mime from 'mime';
const jpeg = require('jpeg-js'); import path from 'path';
const pixelmatch = require('pixelmatch'); import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
module.exports = { compare }; interface DiffFile {
diff: string | Buffer;
ext?: string;
}
const GoldenComparators = { const GoldenComparators = new Map<
'image/png': compareImages, string,
'image/jpeg': compareImages, (
'text/plain': compareText, actualBuffer: string | Buffer,
expectedBuffer: string | Buffer,
mimeType: string
) => DiffFile | undefined
>();
const addSuffix = (
filePath: string,
suffix: string,
customExtension?: string
): string => {
const dirname = path.dirname(filePath);
const ext = path.extname(filePath);
const name = path.basename(filePath, ext);
return path.join(dirname, name + suffix + (customExtension || ext));
}; };
/** const compareImages = (
* @param {?Object} actualBuffer actualBuffer: string | Buffer,
* @param {!Buffer} expectedBuffer expectedBuffer: string | Buffer,
* @param {!string} mimeType mimeType: string
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} ): DiffFile | undefined => {
*/ assert(typeof actualBuffer !== 'string');
function compareImages(actualBuffer, expectedBuffer, mimeType) { assert(typeof expectedBuffer !== 'string');
if (!actualBuffer || !(actualBuffer instanceof Buffer)) {
return { errorMessage: 'Actual result should be Buffer.' };
}
const actual = const actual =
mimeType === 'image/png' mimeType === 'image/png'
? PNG.sync.read(actualBuffer) ? PNG.sync.read(actualBuffer)
: jpeg.decode(actualBuffer); : jpeg.decode(actualBuffer);
const expected = const expected =
mimeType === 'image/png' mimeType === 'image/png'
? PNG.sync.read(expectedBuffer) ? PNG.sync.read(expectedBuffer)
: jpeg.decode(expectedBuffer); : jpeg.decode(expectedBuffer);
if (expected.width !== actual.width || expected.height !== actual.height) { if (expected.width !== actual.width || expected.height !== actual.height) {
return { throw new Error(
errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. `, `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px.`
}; );
} }
const diff = new PNG({ width: expected.width, height: expected.height }); const diff = new PNG({ width: expected.width, height: expected.height });
const count = pixelmatch( const count = pixelmatch(
@ -63,64 +78,68 @@ function compareImages(actualBuffer, expectedBuffer, mimeType) {
expected.height, expected.height,
{ threshold: 0.1 } { threshold: 0.1 }
); );
return count > 0 ? { diff: PNG.sync.write(diff) } : null; return count > 0 ? { diff: PNG.sync.write(diff) } : undefined;
} };
/** const compareText = (
* @param {?Object} actual actual: string | Buffer,
* @param {!Buffer} expectedBuffer expectedBuffer: string | Buffer
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} ): DiffFile | undefined => {
*/ assert(typeof actual === 'string');
function compareText(actual, expectedBuffer) {
if (typeof actual !== 'string') {
return { errorMessage: 'Actual result should be string' };
}
const expected = expectedBuffer.toString('utf-8'); const expected = expectedBuffer.toString('utf-8');
if (expected === actual) { if (expected === actual) {
return null; return;
} }
const diff = new Diff(); const result = diff.diffLines(expected, actual);
const result = diff.main(expected, actual); const html = result.reduce((text, change) => {
diff.cleanupSemantic(result); text += change.added
let html = diff.prettyHtml(result); ? `<span class='ins'>${change.value}</span>`
const diffStylePath = path.join(__dirname, 'diffstyle.css'); : change.removed
html = `<link rel="stylesheet" href="file://${diffStylePath}">` + html; ? `<span class='del'>${change.value}</span>`
: change.value;
return text;
}, `<link rel="stylesheet" href="file://${path.join(__dirname, 'diffstyle.css')}">`);
return { return {
diff: html, diff: html,
diffExtension: '.html', ext: '.html',
}; };
} };
/** GoldenComparators.set('image/png', compareImages);
* @param {?Object} actual GoldenComparators.set('image/jpeg', compareImages);
* @param {string} goldenName GoldenComparators.set('text/plain', compareText);
* @returns {!{pass: boolean, message: (undefined|string)}}
*/ export const compare = (
function compare(goldenPath, outputPath, actual, goldenName) { goldenPath: string,
outputPath: string,
actual: string | Buffer,
goldenName: string
): { pass: true } | { pass: false; message: string } => {
goldenPath = path.normalize(goldenPath); goldenPath = path.normalize(goldenPath);
outputPath = path.normalize(outputPath); outputPath = path.normalize(outputPath);
const expectedPath = path.join(goldenPath, goldenName); const expectedPath = path.join(goldenPath, goldenName);
const actualPath = path.join(outputPath, goldenName); const actualPath = path.join(outputPath, goldenName);
const messageSuffix = const messageSuffix = `Output is saved in "${path.basename(
'Output is saved in "' + path.basename(outputPath + '" directory'); outputPath + '" directory'
)}`;
if (!fs.existsSync(expectedPath)) { if (!fs.existsSync(expectedPath)) {
ensureOutputDir(); ensureOutputDir();
fs.writeFileSync(actualPath, actual); fs.writeFileSync(actualPath, actual);
return { return {
pass: false, pass: false,
message: goldenName + ' is missing in golden results. ' + messageSuffix, message: `${goldenName} is missing in golden results. ${messageSuffix}`,
}; };
} }
const expected = fs.readFileSync(expectedPath); const expected = fs.readFileSync(expectedPath);
const mimeType = mime.getType(goldenName); const mimeType = mime.getType(goldenName);
const comparator = GoldenComparators[mimeType]; assert(mimeType);
const comparator = GoldenComparators.get(mimeType);
if (!comparator) { if (!comparator) {
return { return {
pass: false, pass: false,
message: message: `Failed to find comparator with type ${mimeType}: ${goldenName}`,
'Failed to find comparator with type ' + mimeType + ': ' + goldenName,
}; };
} }
const result = comparator(actual, expected, mimeType); const result = comparator(actual, expected, mimeType);
@ -135,18 +154,14 @@ function compare(goldenPath, outputPath, actual, goldenName) {
// Copy expected to the output/ folder for convenience. // Copy expected to the output/ folder for convenience.
fs.writeFileSync(addSuffix(actualPath, '-expected'), expected); fs.writeFileSync(addSuffix(actualPath, '-expected'), expected);
} }
if (result.diff) { if (result) {
const diffPath = addSuffix(actualPath, '-diff', result.diffExtension); const diffPath = addSuffix(actualPath, '-diff', result.ext);
fs.writeFileSync(diffPath, result.diff); fs.writeFileSync(diffPath, result.diff);
} }
let message = goldenName + ' mismatch!';
if (result.errorMessage) {
message += ' ' + result.errorMessage;
}
return { return {
pass: false, pass: false,
message: message + ' ' + messageSuffix, message: `${goldenName} mismatch! ${messageSuffix}`,
}; };
function ensureOutputDir() { function ensureOutputDir() {
@ -154,17 +169,4 @@ function compare(goldenPath, outputPath, actual, goldenName) {
fs.mkdirSync(outputPath); fs.mkdirSync(outputPath);
} }
} }
} };
/**
* @param {string} filePath
* @param {string} suffix
* @param {string=} customExtension
* @returns {string}
*/
function addSuffix(filePath, suffix, customExtension) {
const dirname = path.dirname(filePath);
const ext = path.extname(filePath);
const name = path.basename(filePath, ext);
return path.join(dirname, name + suffix + (customExtension || ext));
}

View File

@ -14,17 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
import path from 'path';
import os from 'os';
import fs from 'fs';
import { promisify } from 'util';
import expect from 'expect'; import expect from 'expect';
import { import fs from 'fs';
getTestState, import os from 'os';
describeChromeOnly, import path from 'path';
itFailsWindows,
} from './mocha-utils'; // eslint-disable-line import/extensions
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import { promisify } from 'util';
import {
PuppeteerLaunchOptions,
PuppeteerNode,
} from '../../lib/cjs/puppeteer/node/Puppeteer.js';
import {
describeChromeOnly,
getTestState,
itFailsWindows,
} from './mocha-utils.js';
const rmAsync = promisify(rimraf); const rmAsync = promisify(rimraf);
const mkdtempAsync = promisify(fs.mkdtemp); const mkdtempAsync = promisify(fs.mkdtemp);
@ -39,12 +43,22 @@ describeChromeOnly('headful tests', function () {
*/ */
this.timeout(20 * 1000); this.timeout(20 * 1000);
let headfulOptions; let headfulOptions: PuppeteerLaunchOptions | undefined;
let headlessOptions; let headlessOptions: PuppeteerLaunchOptions & { headless: boolean };
let extensionOptions; let extensionOptions: PuppeteerLaunchOptions & {
let forcedOopifOptions; headless: boolean;
let devtoolsOptions; args: string[];
const browsers = []; };
let forcedOopifOptions: PuppeteerLaunchOptions & {
headless: boolean;
devtools: boolean;
args: string[];
};
let devtoolsOptions: PuppeteerLaunchOptions & {
headless: boolean;
devtools: boolean;
};
const browsers: any[] = [];
beforeEach(() => { beforeEach(() => {
const { server, defaultBrowserOptions } = getTestState(); const { server, defaultBrowserOptions } = getTestState();
@ -81,7 +95,7 @@ describeChromeOnly('headful tests', function () {
}); });
}); });
async function launchBrowser(puppeteer, options) { async function launchBrowser(puppeteer: PuppeteerNode, options: any) {
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
browsers.push(browser); browsers.push(browser);
return browser; return browser;
@ -106,7 +120,9 @@ describeChromeOnly('headful tests', function () {
); );
const page = await browserWithExtension.newPage(); const page = await browserWithExtension.newPage();
const backgroundPageTarget = await browserWithExtension.waitForTarget( const backgroundPageTarget = await browserWithExtension.waitForTarget(
(target) => target.type() === 'background_page' (target: { type: () => string }) => {
return target.type() === 'background_page';
}
); );
await page.close(); await page.close();
await browserWithExtension.close(); await browserWithExtension.close();
@ -119,11 +135,21 @@ describeChromeOnly('headful tests', function () {
extensionOptions extensionOptions
); );
const backgroundPageTarget = await browserWithExtension.waitForTarget( const backgroundPageTarget = await browserWithExtension.waitForTarget(
(target) => target.type() === 'background_page' (target: { type: () => string }) => {
return target.type() === 'background_page';
}
); );
const page = await backgroundPageTarget.page(); const page = (await backgroundPageTarget.page())!;
expect(await page.evaluate(() => 2 * 3)).toBe(6); expect(
expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42); await page.evaluate(() => {
return 2 * 3;
})
).toBe(6);
expect(
await page.evaluate(() => {
return (globalThis as any).MAGIC;
})
).toBe(42);
await browserWithExtension.close(); await browserWithExtension.close();
}); });
it('target.page() should return a DevTools page if custom isPageTarget is provided', async function () { it('target.page() should return a DevTools page if custom isPageTarget is provided', async function () {
@ -140,18 +166,24 @@ describeChromeOnly('headful tests', function () {
); );
}, },
}); });
const devtoolsPageTarget = await browser.waitForTarget( const devtoolsPageTarget = await browser.waitForTarget((target) => {
(target) => target.type() === 'other' return target.type() === 'other';
); });
const page = await devtoolsPageTarget.page(); const page = (await devtoolsPageTarget.page())!;
expect(await page.evaluate(() => 2 * 3)).toBe(6); expect(
await page.evaluate(() => {
return 2 * 3;
})
).toBe(6);
expect(await browser.pages()).toContainEqual(page); expect(await browser.pages()).toContainEqual(page);
await browser.close(); await browser.close();
}); });
it('should have default url when launching browser', async function () { it('should have default url when launching browser', async function () {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
const browser = await launchBrowser(puppeteer, extensionOptions); const browser = await launchBrowser(puppeteer, extensionOptions);
const pages = (await browser.pages()).map((page) => page.url()); const pages = (await browser.pages()).map((page: { url: () => any }) => {
return page.url();
});
expect(pages).toEqual(['about:blank']); expect(pages).toEqual(['about:blank']);
await browser.close(); await browser.close();
}); });
@ -169,11 +201,10 @@ describeChromeOnly('headful tests', function () {
); );
const headfulPage = await headfulBrowser.newPage(); const headfulPage = await headfulBrowser.newPage();
await headfulPage.goto(server.EMPTY_PAGE); await headfulPage.goto(server.EMPTY_PAGE);
await headfulPage.evaluate( await headfulPage.evaluate(() => {
() => return (document.cookie =
(document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') });
);
await headfulBrowser.close(); await headfulBrowser.close();
// Read the cookie from headless chrome // Read the cookie from headless chrome
const headlessBrowser = await launchBrowser( const headlessBrowser = await launchBrowser(
@ -182,7 +213,9 @@ describeChromeOnly('headful tests', function () {
); );
const headlessPage = await headlessBrowser.newPage(); const headlessPage = await headlessBrowser.newPage();
await headlessPage.goto(server.EMPTY_PAGE); await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie); const cookie = await headlessPage.evaluate(() => {
return document.cookie;
});
await headlessBrowser.close(); await headlessBrowser.close();
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
await rmAsync(userDataDir).catch(() => {}); await rmAsync(userDataDir).catch(() => {});
@ -198,17 +231,23 @@ describeChromeOnly('headful tests', function () {
const page = await browser.newPage(); const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (r) => r.respond({ body: 'YO, GOOGLE.COM' })); page.on('request', (r: { respond: (arg0: { body: string }) => any }) => {
return r.respond({ body: 'YO, GOOGLE.COM' });
});
await page.evaluate(() => { await page.evaluate(() => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');
frame.setAttribute('src', 'https://google.com/'); frame.setAttribute('src', 'https://google.com/');
document.body.appendChild(frame); document.body.appendChild(frame);
return new Promise((x) => (frame.onload = x)); return new Promise((x) => {
return (frame.onload = x);
});
}); });
await page.waitForSelector('iframe[src="https://google.com/"]'); await page.waitForSelector('iframe[src="https://google.com/"]');
const urls = page const urls = page
.frames() .frames()
.map((frame) => frame.url()) .map((frame: { url: () => any }) => {
return frame.url();
})
.sort(); .sort();
expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']); expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']);
await browser.close(); await browser.close();
@ -221,26 +260,32 @@ describeChromeOnly('headful tests', function () {
// Setup our session listeners to observe OOPIF activity. // Setup our session listeners to observe OOPIF activity.
const session = await page.target().createCDPSession(); const session = await page.target().createCDPSession();
const networkEvents = []; const networkEvents: any[] = [];
const otherSessions = []; const otherSessions: any[] = [];
await session.send('Target.setAutoAttach', { await session.send('Target.setAutoAttach', {
autoAttach: true, autoAttach: true,
flatten: true, flatten: true,
waitForDebuggerOnStart: true, waitForDebuggerOnStart: true,
}); });
session.on('sessionattached', async (session) => { session.on(
'sessionattached',
async (session: {
on: (arg0: string, arg1: (params: any) => number) => void;
send: (arg0: string) => any;
}) => {
otherSessions.push(session); otherSessions.push(session);
session.on('Network.requestWillBeSent', (params) => session.on('Network.requestWillBeSent', (params: any) => {
networkEvents.push(params) return networkEvents.push(params);
); });
await session.send('Network.enable'); await session.send('Network.enable');
await session.send('Runtime.runIfWaitingForDebugger'); await session.send('Runtime.runIfWaitingForDebugger');
}); }
);
// Navigate to the empty page and add an OOPIF iframe with at least one request. // Navigate to the empty page and add an OOPIF iframe with at least one request.
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate((frameUrl) => { await page.evaluate((frameUrl: string) => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');
frame.setAttribute('src', frameUrl); frame.setAttribute('src', frameUrl);
document.body.appendChild(frame); document.body.appendChild(frame);
@ -255,14 +300,16 @@ describeChromeOnly('headful tests', function () {
expect(otherSessions).toHaveLength(1); expect(otherSessions).toHaveLength(1);
// Resume the iframe and trigger another request. // Resume the iframe and trigger another request.
const iframeSession = otherSessions[0]; const iframeSession = otherSessions[0]!;
await iframeSession.send('Runtime.evaluate', { await iframeSession.send('Runtime.evaluate', {
expression: `fetch('/fetch')`, expression: `fetch('/fetch')`,
awaitPromise: true, awaitPromise: true,
}); });
await browser.close(); await browser.close();
const requests = networkEvents.map((event) => event.request.url); const requests = networkEvents.map((event) => {
return event.request.url;
});
expect(requests).toContain(`http://oopifdomain:${server.PORT}/fetch`); expect(requests).toContain(`http://oopifdomain:${server.PORT}/fetch`);
}); });
it('should close browser with beforeunload page', async () => { it('should close browser with beforeunload page', async () => {
@ -286,7 +333,9 @@ describeChromeOnly('headful tests', function () {
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
await Promise.all([ await Promise.all([
context.newPage(), context.newPage(),
browser.waitForTarget((target) => target.url().includes('devtools://')), browser.waitForTarget((target: { url: () => string | string[] }) => {
return target.url().includes('devtools://');
}),
]); ]);
await browser.close(); await browser.close();
}); });
@ -300,20 +349,28 @@ describeChromeOnly('headful tests', function () {
const page2 = await browser.newPage(); const page2 = await browser.newPage();
await page1.bringToFront(); await page1.bringToFront();
expect(await page1.evaluate(() => document.visibilityState)).toBe( expect(
'visible' await page1.evaluate(() => {
); return document.visibilityState;
expect(await page2.evaluate(() => document.visibilityState)).toBe( })
'hidden' ).toBe('visible');
); expect(
await page2.evaluate(() => {
return document.visibilityState;
})
).toBe('hidden');
await page2.bringToFront(); await page2.bringToFront();
expect(await page1.evaluate(() => document.visibilityState)).toBe( expect(
'hidden' await page1.evaluate(() => {
); return document.visibilityState;
expect(await page2.evaluate(() => document.visibilityState)).toBe( })
'visible' ).toBe('hidden');
); expect(
await page2.evaluate(() => {
return document.visibilityState;
})
).toBe('visible');
await page1.close(); await page1.close();
await page2.close(); await page2.close();
@ -340,7 +397,7 @@ describeChromeOnly('headful tests', function () {
const promises = []; const promises = [];
for (let i = 0; i < N; ++i) { for (let i = 0; i < N; ++i) {
promises.push( promises.push(
pages[i].screenshot({ pages[i]!.screenshot({
clip: { x: 50 * i, y: 0, width: 50, height: 50 }, clip: { x: 50 * i, y: 0, width: 50, height: 50 },
}) })
); );
@ -349,7 +406,11 @@ describeChromeOnly('headful tests', function () {
for (let i = 0; i < N; ++i) { for (let i = 0; i < N; ++i) {
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`); expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
} }
await Promise.all(pages.map((page) => page.close())); await Promise.all(
pages.map((page) => {
return page.close();
})
);
await browser.close(); await browser.close();
}); });

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describeFailsFirefox('Emulate idle state', () => { describeFailsFirefox('Emulate idle state', () => {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -29,7 +29,7 @@ describeFailsFirefox('Emulate idle state', () => {
async function getIdleState() { async function getIdleState() {
const { page } = getTestState(); const { page } = getTestState();
const stateElement = await page.$('#state'); const stateElement = (await page.$('#state'))!;
return await page.evaluate((element: HTMLElement) => { return await page.evaluate((element: HTMLElement) => {
return element.innerText; return element.innerText;
}, stateElement); }, stateElement);

View File

@ -16,20 +16,26 @@
import expect from 'expect'; import expect from 'expect';
import { TLSSocket } from 'tls'; import { TLSSocket } from 'tls';
import {
Browser,
BrowserContext,
} from '../../lib/cjs/puppeteer/common/Browser.js';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { HTTPResponse } from '../../lib/cjs/puppeteer/common/HTTPResponse.js';
import { import {
getTestState, getTestState,
describeFailsFirefox, describeFailsFirefox,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('ignoreHTTPSErrors', function () { describe('ignoreHTTPSErrors', function () {
/* Note that this test creates its own browser rather than use /* Note that this test creates its own browser rather than use
* the one provided by the test set-up as we need one * the one provided by the test set-up as we need one
* with ignoreHTTPSErrors set to true * with ignoreHTTPSErrors set to true
*/ */
let browser; let browser!: Browser;
let context; let context: BrowserContext;
let page; let page!: Page;
before(async () => { before(async () => {
const { defaultBrowserOptions, puppeteer } = getTestState(); const { defaultBrowserOptions, puppeteer } = getTestState();
@ -42,7 +48,6 @@ describe('ignoreHTTPSErrors', function () {
after(async () => { after(async () => {
await browser.close(); await browser.close();
browser = null;
}); });
beforeEach(async () => { beforeEach(async () => {
@ -52,8 +57,6 @@ describe('ignoreHTTPSErrors', function () {
afterEach(async () => { afterEach(async () => {
await context.close(); await context.close();
context = null;
page = null;
}); });
describeFailsFirefox('Response.securityDetails', function () { describeFailsFirefox('Response.securityDetails', function () {
@ -64,10 +67,10 @@ describe('ignoreHTTPSErrors', function () {
httpsServer.waitForRequest('/empty.html'), httpsServer.waitForRequest('/empty.html'),
page.goto(httpsServer.EMPTY_PAGE), page.goto(httpsServer.EMPTY_PAGE),
]); ]);
const securityDetails = response.securityDetails(); const securityDetails = response!.securityDetails()!;
expect(securityDetails.issuer()).toBe('puppeteer-tests'); expect(securityDetails.issuer()).toBe('puppeteer-tests');
const protocol = (serverRequest.socket as TLSSocket) const protocol = (serverRequest.socket as TLSSocket)
.getProtocol() .getProtocol()!
.replace('v', ' '); .replace('v', ' ');
expect(securityDetails.protocol()).toBe(protocol); expect(securityDetails.protocol()).toBe(protocol);
expect(securityDetails.subjectName()).toBe('puppeteer-tests'); expect(securityDetails.subjectName()).toBe('puppeteer-tests');
@ -81,24 +84,26 @@ describe('ignoreHTTPSErrors', function () {
it('should be |null| for non-secure requests', async () => { it('should be |null| for non-secure requests', async () => {
const { server } = getTestState(); const { server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.securityDetails()).toBe(null); expect(response.securityDetails()).toBe(null);
}); });
it('Network redirects should report SecurityDetails', async () => { it('Network redirects should report SecurityDetails', async () => {
const { httpsServer } = getTestState(); const { httpsServer } = getTestState();
httpsServer.setRedirect('/plzredirect', '/empty.html'); httpsServer.setRedirect('/plzredirect', '/empty.html');
const responses = []; const responses: HTTPResponse[] = [];
page.on('response', (response) => responses.push(response)); page.on('response', (response) => {
return responses.push(response);
});
const [serverRequest] = await Promise.all([ const [serverRequest] = await Promise.all([
httpsServer.waitForRequest('/plzredirect'), httpsServer.waitForRequest('/plzredirect'),
page.goto(httpsServer.PREFIX + '/plzredirect'), page.goto(httpsServer.PREFIX + '/plzredirect'),
]); ]);
expect(responses.length).toBe(2); expect(responses.length).toBe(2);
expect(responses[0].status()).toBe(302); expect(responses[0]!.status()).toBe(302);
const securityDetails = responses[0].securityDetails(); const securityDetails = responses[0]!.securityDetails()!;
const protocol = (serverRequest.socket as TLSSocket) const protocol = (serverRequest.socket as TLSSocket)
.getProtocol() .getProtocol()!
.replace('v', ' '); .replace('v', ' ');
expect(securityDetails.protocol()).toBe(protocol); expect(securityDetails.protocol()).toBe(protocol);
}); });
@ -107,25 +112,27 @@ describe('ignoreHTTPSErrors', function () {
it('should work', async () => { it('should work', async () => {
const { httpsServer } = getTestState(); const { httpsServer } = getTestState();
let error = null; let error!: Error;
const response = await page const response = await page.goto(httpsServer.EMPTY_PAGE).catch((error_) => {
.goto(httpsServer.EMPTY_PAGE) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBe(null); expect(error).toBeUndefined();
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
itFailsFirefox('should work with request interception', async () => { itFailsFirefox('should work with request interception', async () => {
const { httpsServer } = getTestState(); const { httpsServer } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
const response = await page.goto(httpsServer.EMPTY_PAGE); return request.continue();
});
const response = (await page.goto(httpsServer.EMPTY_PAGE))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
itFailsFirefox('should work with mixed content', async () => { itFailsFirefox('should work with mixed content', async () => {
const { server, httpsServer } = getTestState(); const { server, httpsServer } = getTestState();
httpsServer.setRoute('/mixedcontent.html', (req, res) => { httpsServer.setRoute('/mixedcontent.html', (_req, res) => {
res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`); res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
}); });
await page.goto(httpsServer.PREFIX + '/mixedcontent.html', { await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {
@ -134,7 +141,7 @@ describe('ignoreHTTPSErrors', function () {
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
// Make sure blocked iframe has functional execution context // Make sure blocked iframe has functional execution context
// @see https://github.com/puppeteer/puppeteer/issues/2709 // @see https://github.com/puppeteer/puppeteer/issues/2709
expect(await page.frames()[0].evaluate('1 + 2')).toBe(3); expect(await page.frames()[0]!.evaluate('1 + 2')).toBe(3);
expect(await page.frames()[1].evaluate('2 + 3')).toBe(5); expect(await page.frames()[1]!.evaluate('2 + 3')).toBe(5);
}); });
}); });

View File

@ -21,7 +21,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
const FILE_TO_UPLOAD = path.join(__dirname, '/../assets/file-to-upload.txt'); const FILE_TO_UPLOAD = path.join(__dirname, '/../assets/file-to-upload.txt');
@ -35,33 +35,42 @@ describe('input tests', function () {
await page.goto(server.PREFIX + '/input/fileupload.html'); await page.goto(server.PREFIX + '/input/fileupload.html');
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
const input = await page.$('input'); const input = (await page.$('input'))!;
await page.evaluate((e: HTMLElement) => { await page.evaluate((e: HTMLElement) => {
globalThis._inputEvents = []; (globalThis as any)._inputEvents = [];
e.addEventListener('change', (ev) => e.addEventListener('change', (ev) => {
globalThis._inputEvents.push(ev.type) return (globalThis as any)._inputEvents.push(ev.type);
); });
e.addEventListener('input', (ev) => e.addEventListener('input', (ev) => {
globalThis._inputEvents.push(ev.type) return (globalThis as any)._inputEvents.push(ev.type);
); });
}, input); }, input);
await input.uploadFile(filePath); await input.uploadFile(filePath);
expect( expect(
await page.evaluate((e: HTMLInputElement) => e.files[0].name, input) await page.evaluate((e: HTMLInputElement) => {
return e.files![0]!.name;
}, input)
).toBe('file-to-upload.txt'); ).toBe('file-to-upload.txt');
expect( expect(
await page.evaluate((e: HTMLInputElement) => e.files[0].type, input) await page.evaluate((e: HTMLInputElement) => {
return e.files![0]!.type;
}, input)
).toBe('text/plain'); ).toBe('text/plain');
expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([ expect(
'input', await page.evaluate(() => {
'change', return (globalThis as any)._inputEvents;
]); })
).toEqual(['input', 'change']);
expect( expect(
await page.evaluate((e: HTMLInputElement) => { await page.evaluate((e: HTMLInputElement) => {
const reader = new FileReader(); const reader = new FileReader();
const promise = new Promise((fulfill) => (reader.onload = fulfill)); const promise = new Promise((fulfill) => {
reader.readAsText(e.files[0]); return (reader.onload = fulfill);
return promise.then(() => reader.result); });
reader.readAsText(e.files![0]!);
return promise.then(() => {
return reader.result;
});
}, input) }, input)
).toBe('contents of the file'); ).toBe('contents of the file');
}); });
@ -94,28 +103,30 @@ describe('input tests', function () {
it('should respect timeout', async () => { it('should respect timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
let error = null; let error!: Error;
await page await page.waitForFileChooser({ timeout: 1 }).catch((error_) => {
.waitForFileChooser({ timeout: 1 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should respect default timeout when there is no custom timeout', async () => { it('should respect default timeout when there is no custom timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
page.setDefaultTimeout(1); page.setDefaultTimeout(1);
let error = null; let error!: Error;
await page.waitForFileChooser().catch((error_) => (error = error_)); await page.waitForFileChooser().catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should prioritize exact timeout over default timeout', async () => { it('should prioritize exact timeout over default timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
page.setDefaultTimeout(0); page.setDefaultTimeout(0);
let error = null; let error!: Error;
await page await page.waitForFileChooser({ timeout: 1 }).catch((error_) => {
.waitForFileChooser({ timeout: 1 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should work with no timeout', async () => { it('should work with no timeout', async () => {
@ -123,13 +134,13 @@ describe('input tests', function () {
const [chooser] = await Promise.all([ const [chooser] = await Promise.all([
page.waitForFileChooser({ timeout: 0 }), page.waitForFileChooser({ timeout: 0 }),
page.evaluate(() => page.evaluate(() => {
setTimeout(() => { return setTimeout(() => {
const el = document.createElement('input'); const el = document.createElement('input');
el.type = 'file'; el.type = 'file';
el.click(); el.click();
}, 50) }, 50);
), }),
]); ]);
expect(chooser).toBeTruthy(); expect(chooser).toBeTruthy();
}); });
@ -140,7 +151,9 @@ describe('input tests', function () {
const [fileChooser1, fileChooser2] = await Promise.all([ const [fileChooser1, fileChooser2] = await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
page.waitForFileChooser(), page.waitForFileChooser(),
page.$eval('input', (input: HTMLInputElement) => input.click()), page.$eval('input', (input) => {
return (input as HTMLInputElement).click();
}),
]); ]);
expect(fileChooser1 === fileChooser2).toBe(true); expect(fileChooser1 === fileChooser2).toBe(true);
}); });
@ -159,36 +172,43 @@ describe('input tests', function () {
]); ]);
await Promise.all([ await Promise.all([
chooser.accept([FILE_TO_UPLOAD]), chooser.accept([FILE_TO_UPLOAD]),
new Promise((x) => page.once('metrics', x)), new Promise((x) => {
return page.once('metrics', x);
}),
]); ]);
expect( expect(
await page.$eval( await page.$eval('input', (input) => {
'input', return (input as HTMLInputElement).files!.length;
(input: HTMLInputElement) => input.files.length })
)
).toBe(1); ).toBe(1);
expect( expect(
await page.$eval( await page.$eval('input', (input) => {
'input', return (input as HTMLInputElement).files![0]!.name;
(input: HTMLInputElement) => input.files[0].name })
)
).toBe('file-to-upload.txt'); ).toBe('file-to-upload.txt');
}); });
it('should be able to read selected file', async () => { it('should be able to read selected file', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
page page.waitForFileChooser().then((chooser) => {
.waitForFileChooser() return chooser.accept([FILE_TO_UPLOAD]);
.then((chooser) => chooser.accept([FILE_TO_UPLOAD])); });
expect( expect(
await page.$eval('input', async (picker: HTMLInputElement) => { await page.$eval('input', async (picker) => {
picker.click(); const pick = picker as HTMLInputElement;
await new Promise((x) => (picker.oninput = x)); pick.click();
await new Promise((x) => {
return (pick.oninput = x);
});
const reader = new FileReader(); const reader = new FileReader();
const promise = new Promise((fulfill) => (reader.onload = fulfill)); const promise = new Promise((fulfill) => {
reader.readAsText(picker.files[0]); return (reader.onload = fulfill);
return promise.then(() => reader.result); });
reader.readAsText(pick.files![0]!);
return promise.then(() => {
return reader.result;
});
}) })
).toBe('contents of the file'); ).toBe('contents of the file');
}); });
@ -196,22 +216,30 @@ describe('input tests', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
page page.waitForFileChooser().then((chooser) => {
.waitForFileChooser() return chooser.accept([FILE_TO_UPLOAD]);
.then((chooser) => chooser.accept([FILE_TO_UPLOAD])); });
expect( expect(
await page.$eval('input', async (picker: HTMLInputElement) => { await page.$eval('input', async (picker) => {
picker.click(); const pick = picker as HTMLInputElement;
await new Promise((x) => (picker.oninput = x)); pick.click();
return picker.files.length; await new Promise((x) => {
return (pick.oninput = x);
});
return pick.files!.length;
}) })
).toBe(1); ).toBe(1);
page.waitForFileChooser().then((chooser) => chooser.accept([])); page.waitForFileChooser().then((chooser) => {
return chooser.accept([]);
});
expect( expect(
await page.$eval('input', async (picker: HTMLInputElement) => { await page.$eval('input', async (picker) => {
picker.click(); const pick = picker as HTMLInputElement;
await new Promise((x) => (picker.oninput = x)); pick.click();
return picker.files.length; await new Promise((x) => {
return (pick.oninput = x);
});
return pick.files!.length;
}) })
).toBe(0); ).toBe(0);
}); });
@ -223,7 +251,7 @@ describe('input tests', function () {
page.waitForFileChooser(), page.waitForFileChooser(),
page.click('input'), page.click('input'),
]); ]);
let error = null; let error!: Error;
await chooser await chooser
.accept([ .accept([
path.relative( path.relative(
@ -232,7 +260,9 @@ describe('input tests', function () {
), ),
path.relative(process.cwd(), __dirname + '/../assets/pptr.png'), path.relative(process.cwd(), __dirname + '/../assets/pptr.png'),
]) ])
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).not.toBe(null); expect(error).not.toBe(null);
}); });
it('should succeed even for non-existent files', async () => { it('should succeed even for non-existent files', async () => {
@ -243,27 +273,34 @@ describe('input tests', function () {
page.waitForFileChooser(), page.waitForFileChooser(),
page.click('input'), page.click('input'),
]); ]);
let error: Error | undefined; let error!: Error;
await chooser await chooser.accept(['file-does-not-exist.txt']).catch((error_) => {
.accept(['file-does-not-exist.txt']) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeUndefined(); expect(error).toBeUndefined();
}); });
it('should error on read of non-existent files', async () => { it('should error on read of non-existent files', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
page page.waitForFileChooser().then((chooser) => {
.waitForFileChooser() return chooser.accept(['file-does-not-exist.txt']);
.then((chooser) => chooser.accept(['file-does-not-exist.txt'])); });
expect( expect(
await page.$eval('input', async (picker: HTMLInputElement) => { await page.$eval('input', async (picker) => {
picker.click(); const pick = picker as HTMLInputElement;
await new Promise((x) => (picker.oninput = x)); pick.click();
await new Promise((x) => {
return (pick.oninput = x);
});
const reader = new FileReader(); const reader = new FileReader();
const promise = new Promise((fulfill) => (reader.onerror = fulfill)); const promise = new Promise((fulfill) => {
reader.readAsText(picker.files[0]); return (reader.onerror = fulfill);
return promise.then(() => false); });
reader.readAsText(pick.files![0]!);
return promise.then(() => {
return false;
});
}) })
).toBeFalsy(); ).toBeFalsy();
}); });
@ -273,11 +310,15 @@ describe('input tests', function () {
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
const [fileChooser] = await Promise.all([ const [fileChooser] = await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
page.$eval('input', (input: HTMLInputElement) => input.click()), page.$eval('input', (input) => {
return (input as HTMLInputElement).click();
}),
]); ]);
await fileChooser.accept([]); await fileChooser.accept([]);
let error = null; let error!: Error;
await fileChooser.accept([]).catch((error_) => (error = error_)); await fileChooser.accept([]).catch((error_) => {
return (error = error_);
});
expect(error.message).toBe( expect(error.message).toBe(
'Cannot accept FileChooser which is already handled!' 'Cannot accept FileChooser which is already handled!'
); );
@ -294,13 +335,17 @@ describe('input tests', function () {
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
const [fileChooser1] = await Promise.all([ const [fileChooser1] = await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
page.$eval('input', (input: HTMLInputElement) => input.click()), page.$eval('input', (input) => {
return (input as HTMLInputElement).click();
}),
]); ]);
await fileChooser1.cancel(); await fileChooser1.cancel();
// If this resolves, than we successfully canceled file chooser. // If this resolves, than we successfully canceled file chooser.
await Promise.all([ await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
page.$eval('input', (input: HTMLInputElement) => input.click()), page.$eval('input', (input) => {
return (input as HTMLInputElement).click();
}),
]); ]);
}); });
it('should fail when canceling file chooser twice', async () => { it('should fail when canceling file chooser twice', async () => {
@ -309,15 +354,17 @@ describe('input tests', function () {
await page.setContent(`<input type=file>`); await page.setContent(`<input type=file>`);
const [fileChooser] = await Promise.all([ const [fileChooser] = await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
page.$eval('input', (input: HTMLInputElement) => input.click()), page.$eval('input', (input) => {
return (input as HTMLElement).click();
}),
]); ]);
await fileChooser.cancel(); await fileChooser.cancel();
let error = null; let error!: Error;
try { try {
fileChooser.cancel(); fileChooser.cancel();
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toBe( expect(error.message).toBe(

View File

@ -22,7 +22,7 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
shortWaitForArrayToHaveAtLeastNElements, shortWaitForArrayToHaveAtLeastNElements,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('JSHandle', function () { describe('JSHandle', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -32,24 +32,31 @@ describe('JSHandle', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const windowHandle = await page.evaluateHandle(() => window); const windowHandle = await page.evaluateHandle(() => {
return window;
});
expect(windowHandle).toBeTruthy(); expect(windowHandle).toBeTruthy();
}); });
it('should accept object handle as an argument', async () => { it('should accept object handle as an argument', async () => {
const { page } = getTestState(); const { page } = getTestState();
const navigatorHandle = await page.evaluateHandle(() => navigator); const navigatorHandle = await page.evaluateHandle(() => {
const text = await page.evaluate( return navigator;
(e: Navigator) => e.userAgent, });
navigatorHandle const text = await page.evaluate((e: Navigator) => {
); return e.userAgent;
}, navigatorHandle);
expect(text).toContain('Mozilla'); expect(text).toContain('Mozilla');
}); });
it('should accept object handle to primitive types', async () => { it('should accept object handle to primitive types', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => 5); const aHandle = await page.evaluateHandle(() => {
const isFive = await page.evaluate((e) => Object.is(e, 5), aHandle); return 5;
});
const isFive = await page.evaluate((e) => {
return Object.is(e, 5);
}, aHandle);
expect(isFive).toBeTruthy(); expect(isFive).toBeTruthy();
}); });
it('should warn about recursive objects', async () => { it('should warn about recursive objects', async () => {
@ -57,31 +64,44 @@ describe('JSHandle', function () {
const test: { obj?: unknown } = {}; const test: { obj?: unknown } = {};
test.obj = test; test.obj = test;
let error = null; let error!: Error;
await page await page
.evaluateHandle(
(opts) => {
return opts.elem;
},
// @ts-expect-error we are deliberately passing a bad type here (nested object) // @ts-expect-error we are deliberately passing a bad type here (nested object)
.evaluateHandle((opts) => opts.elem, { test }) { test }
.catch((error_) => (error = error_)); )
.catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Recursive objects are not allowed.'); expect(error.message).toContain('Recursive objects are not allowed.');
}); });
it('should accept object handle to unserializable value', async () => { it('should accept object handle to unserializable value', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => Infinity); const aHandle = await page.evaluateHandle(() => {
expect(await page.evaluate((e) => Object.is(e, Infinity), aHandle)).toBe( return Infinity;
true });
); expect(
await page.evaluate((e) => {
return Object.is(e, Infinity);
}, aHandle)
).toBe(true);
}); });
it('should use the same JS wrappers', async () => { it('should use the same JS wrappers', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => { const aHandle = await page.evaluateHandle(() => {
globalThis.FOO = 123; (globalThis as any).FOO = 123;
return window; return window;
}); });
expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe( expect(
123 await page.evaluate((e: { FOO: number }) => {
); return e.FOO;
}, aHandle)
).toBe(123);
}); });
}); });
@ -89,11 +109,13 @@ describe('JSHandle', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => ({ const aHandle = await page.evaluateHandle(() => {
return {
one: 1, one: 1,
two: 2, two: 2,
three: 3, three: 3,
})); };
});
const twoHandle = await aHandle.getProperty('two'); const twoHandle = await aHandle.getProperty('two');
expect(await twoHandle.jsonValue()).toEqual(2); expect(await twoHandle.jsonValue()).toEqual(2);
}); });
@ -101,11 +123,13 @@ describe('JSHandle', function () {
it('should return a JSHandle even if the property does not exist', async () => { it('should return a JSHandle even if the property does not exist', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => ({ const aHandle = await page.evaluateHandle(() => {
return {
one: 1, one: 1,
two: 2, two: 2,
three: 3, three: 3,
})); };
});
const undefinedHandle = await aHandle.getProperty('doesnotexist'); const undefinedHandle = await aHandle.getProperty('doesnotexist');
expect(undefinedHandle).toBeInstanceOf(JSHandle); expect(undefinedHandle).toBeInstanceOf(JSHandle);
expect(await undefinedHandle.jsonValue()).toBe(undefined); expect(await undefinedHandle.jsonValue()).toBe(undefined);
@ -116,7 +140,9 @@ describe('JSHandle', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => ({ foo: 'bar' })); const aHandle = await page.evaluateHandle(() => {
return { foo: 'bar' };
});
const json = await aHandle.jsonValue<Record<string, string>>(); const json = await aHandle.jsonValue<Record<string, string>>();
expect(json).toEqual({ foo: 'bar' }); expect(json).toEqual({ foo: 'bar' });
}); });
@ -124,7 +150,9 @@ describe('JSHandle', function () {
it('works with jsonValues that are not objects', async () => { it('works with jsonValues that are not objects', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => ['a', 'b']); const aHandle = await page.evaluateHandle(() => {
return ['a', 'b'];
});
const json = await aHandle.jsonValue<string[]>(); const json = await aHandle.jsonValue<string[]>();
expect(json).toEqual(['a', 'b']); expect(json).toEqual(['a', 'b']);
}); });
@ -132,7 +160,9 @@ describe('JSHandle', function () {
it('works with jsonValues that are primitives', async () => { it('works with jsonValues that are primitives', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => 'foo'); const aHandle = await page.evaluateHandle(() => {
return 'foo';
});
const json = await aHandle.jsonValue<string>(); const json = await aHandle.jsonValue<string>();
expect(json).toEqual('foo'); expect(json).toEqual('foo');
}); });
@ -140,9 +170,9 @@ describe('JSHandle', function () {
itFailsFirefox('should not work with dates', async () => { itFailsFirefox('should not work with dates', async () => {
const { page } = getTestState(); const { page } = getTestState();
const dateHandle = await page.evaluateHandle( const dateHandle = await page.evaluateHandle(() => {
() => new Date('2017-09-26T00:00:00.000Z') return new Date('2017-09-26T00:00:00.000Z');
); });
const json = await dateHandle.jsonValue(); const json = await dateHandle.jsonValue();
expect(json).toEqual({}); expect(json).toEqual({});
}); });
@ -150,8 +180,10 @@ describe('JSHandle', function () {
const { page, isChrome } = getTestState(); const { page, isChrome } = getTestState();
const windowHandle = await page.evaluateHandle('window'); const windowHandle = await page.evaluateHandle('window');
let error = null; let error!: Error;
await windowHandle.jsonValue().catch((error_) => (error = error_)); await windowHandle.jsonValue().catch((error_) => {
return (error = error_);
});
if (isChrome) { if (isChrome) {
expect(error.message).toContain('Object reference chain is too long'); expect(error.message).toContain('Object reference chain is too long');
} else { } else {
@ -164,11 +196,13 @@ describe('JSHandle', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => ({ const aHandle = await page.evaluateHandle(() => {
return {
foo: 'bar', foo: 'bar',
})); };
});
const properties = await aHandle.getProperties(); const properties = await aHandle.getProperties();
const foo = properties.get('foo'); const foo = properties.get('foo')!;
expect(foo).toBeTruthy(); expect(foo).toBeTruthy();
expect(await foo.jsonValue()).toBe('bar'); expect(await foo.jsonValue()).toBe('bar');
}); });
@ -192,8 +226,8 @@ describe('JSHandle', function () {
return new B(); return new B();
}); });
const properties = await aHandle.getProperties(); const properties = await aHandle.getProperties();
expect(await properties.get('a').jsonValue()).toBe('1'); expect(await properties.get('a')!.jsonValue()).toBe('1');
expect(await properties.get('b').jsonValue()).toBe('2'); expect(await properties.get('b')!.jsonValue()).toBe('2');
}); });
}); });
@ -201,14 +235,18 @@ describe('JSHandle', function () {
it('should work', async () => { it('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => document.body); const aHandle = await page.evaluateHandle(() => {
return document.body;
});
const element = aHandle.asElement(); const element = aHandle.asElement();
expect(element).toBeTruthy(); expect(element).toBeTruthy();
}); });
it('should return null for non-elements', async () => { it('should return null for non-elements', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => 2); const aHandle = await page.evaluateHandle(() => {
return 2;
});
const element = aHandle.asElement(); const element = aHandle.asElement();
expect(element).toBeFalsy(); expect(element).toBeFalsy();
}); });
@ -216,16 +254,15 @@ describe('JSHandle', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<div>ee!</div>'); await page.setContent('<div>ee!</div>');
const aHandle = await page.evaluateHandle( const aHandle = await page.evaluateHandle(() => {
() => document.querySelector('div').firstChild return document.querySelector('div')!.firstChild;
); });
const element = aHandle.asElement(); const element = aHandle.asElement();
expect(element).toBeTruthy(); expect(element).toBeTruthy();
expect( expect(
await page.evaluate( await page.evaluate((e: HTMLElement) => {
(e: HTMLElement) => e.nodeType === Node.TEXT_NODE, return e.nodeType === Node.TEXT_NODE;
element }, element)
)
); );
}); });
}); });
@ -234,15 +271,21 @@ describe('JSHandle', function () {
it('should work for primitives', async () => { it('should work for primitives', async () => {
const { page } = getTestState(); const { page } = getTestState();
const numberHandle = await page.evaluateHandle(() => 2); const numberHandle = await page.evaluateHandle(() => {
return 2;
});
expect(numberHandle.toString()).toBe('JSHandle:2'); expect(numberHandle.toString()).toBe('JSHandle:2');
const stringHandle = await page.evaluateHandle(() => 'a'); const stringHandle = await page.evaluateHandle(() => {
return 'a';
});
expect(stringHandle.toString()).toBe('JSHandle:a'); expect(stringHandle.toString()).toBe('JSHandle:a');
}); });
it('should work for complicated objects', async () => { it('should work for complicated objects', async () => {
const { page } = getTestState(); const { page } = getTestState();
const aHandle = await page.evaluateHandle(() => window); const aHandle = await page.evaluateHandle(() => {
return window;
});
expect(aHandle.toString()).toBe('JSHandle@object'); expect(aHandle.toString()).toBe('JSHandle@object');
}); });
it('should work with different subtypes', async () => { it('should work with different subtypes', async () => {
@ -315,9 +358,11 @@ describe('JSHandle', function () {
`; `;
}); });
await page.evaluate(async () => { await page.evaluate(async () => {
return new Promise((resolve) => window.requestAnimationFrame(resolve)); return new Promise((resolve) => {
return window.requestAnimationFrame(resolve);
}); });
const divHandle = await page.$('div'); });
const divHandle = (await page.$('div'))!;
expect(await divHandle.clickablePoint()).toEqual({ expect(await divHandle.clickablePoint()).toEqual({
x: 45 + 60, // margin + middle point offset x: 45 + 60, // margin + middle point offset
y: 45 + 30, // margin + middle point offset y: 45 + 30, // margin + middle point offset
@ -343,10 +388,12 @@ describe('JSHandle', function () {
`; `;
}); });
await page.evaluate(async () => { await page.evaluate(async () => {
return new Promise((resolve) => window.requestAnimationFrame(resolve)); return new Promise((resolve) => {
return window.requestAnimationFrame(resolve);
}); });
const frame = page.frames()[1]; });
const divHandle = await frame.$('div'); const frame = page.frames()[1]!;
const divHandle = (await frame.$('div'))!;
expect(await divHandle.clickablePoint()).toEqual({ expect(await divHandle.clickablePoint()).toEqual({
x: 20 + 45 + 60, // iframe pos + margin + middle point offset x: 20 + 45 + 60, // iframe pos + margin + middle point offset
y: 20 + 45 + 30, // iframe pos + margin + middle point offset y: 20 + 45 + 30, // iframe pos + margin + middle point offset
@ -367,7 +414,7 @@ describe('JSHandle', function () {
itFailsFirefox('should work', async () => { itFailsFirefox('should work', async () => {
const { page } = getTestState(); const { page } = getTestState();
const clicks = []; const clicks: [x: number, y: number][] = [];
await page.exposeFunction('reportClick', (x: number, y: number): void => { await page.exposeFunction('reportClick', (x: number, y: number): void => {
clicks.push([x, y]); clicks.push([x, y]);
@ -384,7 +431,7 @@ describe('JSHandle', function () {
}); });
}); });
const divHandle = await page.$('div'); const divHandle = (await page.$('div'))!;
await divHandle.click(); await divHandle.click();
await divHandle.click({ await divHandle.click({
offset: { offset: {

View File

@ -22,7 +22,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { KeyInput } from '../../lib/cjs/puppeteer/common/USKeyboardLayout.js'; import { KeyInput } from '../../lib/cjs/puppeteer/common/USKeyboardLayout.js';
describe('Keyboard', function () { describe('Keyboard', function () {
@ -40,16 +40,20 @@ describe('Keyboard', function () {
const text = 'Hello world. I am the text that was typed!'; const text = 'Hello world. I am the text that was typed!';
await page.keyboard.type(text); await page.keyboard.type(text);
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe(text); ).toBe(text);
}); });
itFailsFirefox('should press the metaKey', async () => { itFailsFirefox('should press the metaKey', async () => {
const { page, isFirefox } = getTestState(); const { page, isFirefox } = getTestState();
await page.evaluate(() => { await page.evaluate(() => {
(window as any).keyPromise = new Promise((resolve) => (window as any).keyPromise = new Promise((resolve) => {
document.addEventListener('keydown', (event) => resolve(event.key)) return document.addEventListener('keydown', (event) => {
); return resolve(event.key);
});
});
}); });
await page.keyboard.press('Meta'); await page.keyboard.press('Meta');
expect(await page.evaluate('keyPromise')).toBe( expect(await page.evaluate('keyPromise')).toBe(
@ -62,14 +66,18 @@ describe('Keyboard', function () {
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', 'Hello World!'); await page.type('textarea', 'Hello World!');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('Hello World!'); ).toBe('Hello World!');
for (let i = 0; i < 'World!'.length; i++) { for (let i = 0; i < 'World!'.length; i++) {
page.keyboard.press('ArrowLeft'); page.keyboard.press('ArrowLeft');
} }
await page.keyboard.type('inserted '); await page.keyboard.type('inserted ');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('Hello inserted World!'); ).toBe('Hello inserted World!');
page.keyboard.down('Shift'); page.keyboard.down('Shift');
for (let i = 0; i < 'inserted '.length; i++) { for (let i = 0; i < 'inserted '.length; i++) {
@ -78,26 +86,38 @@ describe('Keyboard', function () {
page.keyboard.up('Shift'); page.keyboard.up('Shift');
await page.keyboard.press('Backspace'); await page.keyboard.press('Backspace');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('Hello World!'); ).toBe('Hello World!');
}); });
it('should send a character with ElementHandle.press', async () => { it('should send a character with ElementHandle.press', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea'); const textarea = (await page.$('textarea'))!;
await textarea.press('a'); await textarea.press('a');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('a'); ).toBe('a');
await page.evaluate(() => await page.evaluate(() => {
window.addEventListener('keydown', (e) => e.preventDefault(), true) return window.addEventListener(
'keydown',
(e) => {
return e.preventDefault();
},
true
); );
});
await textarea.press('b'); await textarea.press('b');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('a'); ).toBe('a');
}); });
itFailsFirefox( itFailsFirefox(
@ -106,10 +126,12 @@ describe('Keyboard', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea'); const textarea = (await page.$('textarea'))!;
await textarea.press('a', { text: 'ё' }); await textarea.press('a', { text: 'ё' });
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('ё'); ).toBe('ё');
} }
); );
@ -120,14 +142,24 @@ describe('Keyboard', function () {
await page.focus('textarea'); await page.focus('textarea');
await page.keyboard.sendCharacter('嗨'); await page.keyboard.sendCharacter('嗨');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('嗨'); ).toBe('嗨');
await page.evaluate(() => await page.evaluate(() => {
window.addEventListener('keydown', (e) => e.preventDefault(), true) return window.addEventListener(
'keydown',
(e) => {
return e.preventDefault();
},
true
); );
});
await page.keyboard.sendCharacter('a'); await page.keyboard.sendCharacter('a');
expect( expect(
await page.evaluate(() => document.querySelector('textarea').value) await page.evaluate(() => {
return document.querySelector('textarea')!.value;
})
).toBe('嗨a'); ).toBe('嗨a');
}); });
itFailsFirefox('should report shiftKey', async () => { itFailsFirefox('should report shiftKey', async () => {
@ -142,7 +174,11 @@ describe('Keyboard', function () {
]); ]);
for (const [modifierKey, modifierCode] of codeForKey) { for (const [modifierKey, modifierCode] of codeForKey) {
await keyboard.down(modifierKey); await keyboard.down(modifierKey);
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
'Keydown: ' + 'Keydown: ' +
modifierKey + modifierKey +
' ' + ' ' +
@ -156,7 +192,11 @@ describe('Keyboard', function () {
await keyboard.down('!'); await keyboard.down('!');
// Shift+! will generate a keypress // Shift+! will generate a keypress
if (modifierKey === 'Shift') { if (modifierKey === 'Shift') {
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
'Keydown: ! Digit1 49 [' + 'Keydown: ! Digit1 49 [' +
modifierKey + modifierKey +
']\nKeypress: ! Digit1 33 33 [' + ']\nKeypress: ! Digit1 33 33 [' +
@ -164,17 +204,25 @@ describe('Keyboard', function () {
']' ']'
); );
} else { } else {
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keydown: ! Digit1 49 [' + modifierKey + ']' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']');
} }
await keyboard.up('!'); await keyboard.up('!');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keyup: ! Digit1 49 [' + modifierKey + ']' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']');
await keyboard.up(modifierKey); await keyboard.up(modifierKey);
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
'Keyup: ' + 'Keyup: ' +
modifierKey + modifierKey +
' ' + ' ' +
@ -191,36 +239,52 @@ describe('Keyboard', function () {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard; const keyboard = page.keyboard;
await keyboard.down('Control'); await keyboard.down('Control');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keydown: Control ControlLeft 17 [Control]' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keydown: Control ControlLeft 17 [Control]');
await keyboard.down('Alt'); await keyboard.down('Alt');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keydown: Alt AltLeft 18 [Alt Control]' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keydown: Alt AltLeft 18 [Alt Control]');
await keyboard.down(';'); await keyboard.down(';');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keydown: ; Semicolon 186 [Alt Control]' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keydown: ; Semicolon 186 [Alt Control]');
await keyboard.up(';'); await keyboard.up(';');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keyup: ; Semicolon 186 [Alt Control]' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keyup: ; Semicolon 186 [Alt Control]');
await keyboard.up('Control'); await keyboard.up('Control');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keyup: Control ControlLeft 17 [Alt]' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keyup: Control ControlLeft 17 [Alt]');
await keyboard.up('Alt'); await keyboard.up('Alt');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
'Keyup: Alt AltLeft 18 []' await page.evaluate(() => {
); return (globalThis as any).getResult();
})
).toBe('Keyup: Alt AltLeft 18 []');
}); });
it('should send proper codes while typing', async () => { it('should send proper codes while typing', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.type('!'); await page.keyboard.type('!');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
[ [
'Keydown: ! Digit1 49 []', 'Keydown: ! Digit1 49 []',
'Keypress: ! Digit1 33 33 []', 'Keypress: ! Digit1 33 33 []',
@ -228,7 +292,11 @@ describe('Keyboard', function () {
].join('\n') ].join('\n')
); );
await page.keyboard.type('^'); await page.keyboard.type('^');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
[ [
'Keydown: ^ Digit6 54 []', 'Keydown: ^ Digit6 54 []',
'Keypress: ^ Digit6 94 94 []', 'Keypress: ^ Digit6 94 94 []',
@ -243,7 +311,11 @@ describe('Keyboard', function () {
const keyboard = page.keyboard; const keyboard = page.keyboard;
await keyboard.down('Shift'); await keyboard.down('Shift');
await page.keyboard.type('~'); await page.keyboard.type('~');
expect(await page.evaluate(() => globalThis.getResult())).toBe( expect(
await page.evaluate(() => {
return (globalThis as any).getResult();
})
).toBe(
[ [
'Keydown: Shift ShiftLeft 16 [Shift]', 'Keydown: Shift ShiftLeft 16 [Shift]',
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode
@ -275,33 +347,59 @@ describe('Keyboard', function () {
); );
}); });
await page.keyboard.type('Hello World!'); await page.keyboard.type('Hello World!');
expect(await page.evaluate(() => globalThis.textarea.value)).toBe( expect(
'He Wrd!' await page.evaluate(() => {
); return (globalThis as any).textarea.value;
})
).toBe('He Wrd!');
}); });
itFailsFirefox('should specify repeat property', async () => { itFailsFirefox('should specify repeat property', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea'); await page.focus('textarea');
await page.evaluate(() => await page.evaluate(() => {
document return document.querySelector('textarea')!.addEventListener(
.querySelector('textarea') 'keydown',
.addEventListener('keydown', (e) => (globalThis.lastEvent = e), true) (e) => {
return ((globalThis as any).lastEvent = e);
},
true
); );
});
await page.keyboard.down('a'); await page.keyboard.down('a');
expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); expect(
await page.evaluate(() => {
return (globalThis as any).lastEvent.repeat;
})
).toBe(false);
await page.keyboard.press('a'); await page.keyboard.press('a');
expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true); expect(
await page.evaluate(() => {
return (globalThis as any).lastEvent.repeat;
})
).toBe(true);
await page.keyboard.down('b'); await page.keyboard.down('b');
expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); expect(
await page.evaluate(() => {
return (globalThis as any).lastEvent.repeat;
})
).toBe(false);
await page.keyboard.down('b'); await page.keyboard.down('b');
expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true); expect(
await page.evaluate(() => {
return (globalThis as any).lastEvent.repeat;
})
).toBe(true);
await page.keyboard.up('a'); await page.keyboard.up('a');
await page.keyboard.down('a'); await page.keyboard.down('a');
expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); expect(
await page.evaluate(() => {
return (globalThis as any).lastEvent.repeat;
})
).toBe(false);
}); });
itFailsFirefox('should type all kinds of characters', async () => { itFailsFirefox('should type all kinds of characters', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -319,11 +417,13 @@ describe('Keyboard', function () {
await page.evaluate(() => { await page.evaluate(() => {
window.addEventListener( window.addEventListener(
'keydown', 'keydown',
(event) => (globalThis.keyLocation = event.location), (event) => {
return ((globalThis as any).keyLocation = event.location);
},
true true
); );
}); });
const textarea = await page.$('textarea'); const textarea = (await page.$('textarea'))!;
await textarea.press('Digit5'); await textarea.press('Digit5');
expect(await page.evaluate('keyLocation')).toBe(0); expect(await page.evaluate('keyLocation')).toBe(0);
@ -343,15 +443,21 @@ describe('Keyboard', function () {
let error = await page.keyboard let error = await page.keyboard
// @ts-expect-error bad input // @ts-expect-error bad input
.press('NotARealKey') .press('NotARealKey')
.catch((error_) => error_); .catch((error_) => {
return error_;
});
expect(error.message).toBe('Unknown key: "NotARealKey"'); expect(error.message).toBe('Unknown key: "NotARealKey"');
// @ts-expect-error bad input // @ts-expect-error bad input
error = await page.keyboard.press('ё').catch((error_) => error_); error = await page.keyboard.press('ё').catch((error_) => {
return error_;
});
expect(error && error.message).toBe('Unknown key: "ё"'); expect(error && error.message).toBe('Unknown key: "ё"');
// @ts-expect-error bad input // @ts-expect-error bad input
error = await page.keyboard.press('😊').catch((error_) => error_); error = await page.keyboard.press('😊').catch((error_) => {
return error_;
});
expect(error && error.message).toBe('Unknown key: "😊"'); expect(error && error.message).toBe('Unknown key: "😊"');
}); });
itFailsFirefox('should type emoji', async () => { itFailsFirefox('should type emoji', async () => {
@ -360,10 +466,9 @@ describe('Keyboard', function () {
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵'); await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
expect( expect(
await page.$eval( await page.$eval('textarea', (textarea) => {
'textarea', return (textarea as HTMLInputElement).value;
(textarea: HTMLInputElement) => textarea.value })
)
).toBe('👹 Tokyo street Japan 🇯🇵'); ).toBe('👹 Tokyo street Japan 🇯🇵');
}); });
itFailsFirefox('should type emoji into an iframe', async () => { itFailsFirefox('should type emoji into an iframe', async () => {
@ -375,23 +480,22 @@ describe('Keyboard', function () {
'emoji-test', 'emoji-test',
server.PREFIX + '/input/textarea.html' server.PREFIX + '/input/textarea.html'
); );
const frame = page.frames()[1]; const frame = page.frames()[1]!;
const textarea = await frame.$('textarea'); const textarea = (await frame.$('textarea'))!;
await textarea.type('👹 Tokyo street Japan 🇯🇵'); await textarea.type('👹 Tokyo street Japan 🇯🇵');
expect( expect(
await frame.$eval( await frame.$eval('textarea', (textarea) => {
'textarea', return (textarea as HTMLInputElement).value;
(textarea: HTMLInputElement) => textarea.value })
)
).toBe('👹 Tokyo street Japan 🇯🇵'); ).toBe('👹 Tokyo street Japan 🇯🇵');
}); });
itFailsFirefox('should press the meta key', async () => { itFailsFirefox('should press the meta key', async () => {
const { page, isFirefox } = getTestState(); const { page, isFirefox } = getTestState();
await page.evaluate(() => { await page.evaluate(() => {
globalThis.result = null; (globalThis as any).result = null;
document.addEventListener('keydown', (event) => { document.addEventListener('keydown', (event) => {
globalThis.result = [event.key, event.code, event.metaKey]; (globalThis as any).result = [event.key, event.code, event.metaKey];
}); });
}); });
await page.keyboard.press('Meta'); await page.keyboard.press('Meta');

View File

@ -13,24 +13,25 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Protocol from 'devtools-protocol';
import expect from 'expect';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import rimraf from 'rimraf';
import sinon from 'sinon'; import sinon from 'sinon';
import { TLSSocket } from 'tls';
import { promisify } from 'util'; import { promisify } from 'util';
import Protocol from 'devtools-protocol'; import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { Product } from '../../lib/cjs/puppeteer/common/Product.js';
import { import {
getTestState, getTestState,
itChromeOnly, itChromeOnly,
itFailsFirefox, itFailsFirefox,
itFirefoxOnly, itFirefoxOnly,
itOnlyRegularInstall, itOnlyRegularInstall,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import utils from './utils.js'; import utils from './utils.js';
import expect from 'expect';
import rimraf from 'rimraf';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { TLSSocket } from 'tls';
const mkdtempAsync = promisify(fs.mkdtemp); const mkdtempAsync = promisify(fs.mkdtemp);
const readFileAsync = promisify(fs.readFile); const readFileAsync = promisify(fs.readFile);
@ -73,7 +74,7 @@ describe('Launcher specs', function () {
expect(await browserFetcher.canDownload('100000')).toBe(false); expect(await browserFetcher.canDownload('100000')).toBe(false);
expect(await browserFetcher.canDownload(expectedRevision)).toBe(true); expect(await browserFetcher.canDownload(expectedRevision)).toBe(true);
revisionInfo = await browserFetcher.download(expectedRevision); revisionInfo = (await browserFetcher.download(expectedRevision))!;
expect(revisionInfo.local).toBe(true); expect(revisionInfo.local).toBe(true);
expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe(
'LINUX BINARY\n' 'LINUX BINARY\n'
@ -118,7 +119,7 @@ describe('Launcher specs', function () {
expect(await browserFetcher.canDownload('100000')).toBe(false); expect(await browserFetcher.canDownload('100000')).toBe(false);
expect(await browserFetcher.canDownload(expectedVersion)).toBe(true); expect(await browserFetcher.canDownload(expectedVersion)).toBe(true);
revisionInfo = await browserFetcher.download(expectedVersion); revisionInfo = (await browserFetcher.download(expectedVersion))!;
expect(revisionInfo.local).toBe(true); expect(revisionInfo.local).toBe(true);
expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe(
'FIREFOX LINUX BINARY\n' 'FIREFOX LINUX BINARY\n'
@ -147,7 +148,9 @@ describe('Launcher specs', function () {
const page = await remote.newPage(); const page = await remote.newPage();
const navigationPromise = page const navigationPromise = page
.goto(server.PREFIX + '/one-style.html', { timeout: 60000 }) .goto(server.PREFIX + '/one-style.html', { timeout: 60000 })
.catch((error_) => error_); .catch((error_) => {
return error_;
});
await server.waitForRequest('/one-style.css'); await server.waitForRequest('/one-style.css');
remote.disconnect(); remote.disconnect();
const error = await navigationPromise; const error = await navigationPromise;
@ -170,7 +173,9 @@ describe('Launcher specs', function () {
const page = await remote.newPage(); const page = await remote.newPage();
const watchdog = page const watchdog = page
.waitForSelector('div', { timeout: 60000 }) .waitForSelector('div', { timeout: 60000 })
.catch((error_) => error_); .catch((error_) => {
return error_;
});
remote.disconnect(); remote.disconnect();
const error = await watchdog; const error = await watchdog;
expect(error.message).toContain('Protocol error'); expect(error.message).toContain('Protocol error');
@ -187,8 +192,12 @@ describe('Launcher specs', function () {
}); });
const newPage = await remote.newPage(); const newPage = await remote.newPage();
const results = await Promise.all([ const results = await Promise.all([
newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error), newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => {
newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error), return error;
}),
newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => {
return error;
}),
browser.close(), browser.close(),
]); ]);
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
@ -204,10 +213,14 @@ describe('Launcher specs', function () {
const { defaultBrowserOptions, puppeteer } = getTestState(); const { defaultBrowserOptions, puppeteer } = getTestState();
const browser = await puppeteer.launch(defaultBrowserOptions); const browser = await puppeteer.launch(defaultBrowserOptions);
const page = await browser.newPage(); const page = await browser.newPage();
let error = null; let error!: Error;
const neverResolves = page const neverResolves = page
.evaluate(() => new Promise(() => {})) .evaluate(() => {
.catch((error_) => (error = error_)); return new Promise(() => {});
})
.catch((error_) => {
return (error = error_);
});
await browser.close(); await browser.close();
await neverResolves; await neverResolves;
expect(error.message).toContain('Protocol error'); expect(error.message).toContain('Protocol error');
@ -215,11 +228,13 @@ describe('Launcher specs', function () {
it('should reject if executable path is invalid', async () => { it('should reject if executable path is invalid', async () => {
const { defaultBrowserOptions, puppeteer } = getTestState(); const { defaultBrowserOptions, puppeteer } = getTestState();
let waitError = null; let waitError!: Error;
const options = Object.assign({}, defaultBrowserOptions, { const options = Object.assign({}, defaultBrowserOptions, {
executablePath: 'random-invalid-path', executablePath: 'random-invalid-path',
}); });
await puppeteer.launch(options).catch((error) => (waitError = error)); await puppeteer.launch(options).catch((error) => {
return (waitError = error);
});
expect(waitError.message).toContain('Failed to launch'); expect(waitError.message).toContain('Failed to launch');
}); });
it('userDataDir option', async () => { it('userDataDir option', async () => {
@ -315,13 +330,19 @@ describe('Launcher specs', function () {
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
const page = await browser.newPage(); const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => (localStorage.hey = 'hello')); await page.evaluate(() => {
return (localStorage['hey'] = 'hello');
});
await browser.close(); await browser.close();
const browser2 = await puppeteer.launch(options); const browser2 = await puppeteer.launch(options);
const page2 = await browser2.newPage(); const page2 = await browser2.newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); expect(
await page2.evaluate(() => {
return localStorage['hey'];
})
).toBe('hello');
await browser2.close(); await browser2.close();
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
await rmAsync(userDataDir).catch(() => {}); await rmAsync(userDataDir).catch(() => {});
@ -336,19 +357,20 @@ describe('Launcher specs', function () {
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
const page = await browser.newPage(); const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate( await page.evaluate(() => {
() => return (document.cookie =
(document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') });
);
await browser.close(); await browser.close();
const browser2 = await puppeteer.launch(options); const browser2 = await puppeteer.launch(options);
const page2 = await browser2.newPage(); const page2 = await browser2.newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => document.cookie)).toBe( expect(
'doSomethingOnlyOnce=true' await page2.evaluate(() => {
); return document.cookie;
})
).toBe('doSomethingOnlyOnce=true');
await browser2.close(); await browser2.close();
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
await rmAsync(userDataDir).catch(() => {}); await rmAsync(userDataDir).catch(() => {});
@ -422,16 +444,16 @@ describe('Launcher specs', function () {
const browser = await puppeteer.launch( const browser = await puppeteer.launch(
Object.assign({}, defaultBrowserOptions, { Object.assign({}, defaultBrowserOptions, {
// Ignore first and third default argument. // Ignore first and third default argument.
ignoreDefaultArgs: [defaultArgs[0], defaultArgs[2]], ignoreDefaultArgs: [defaultArgs[0]!, defaultArgs[2]],
}) })
); );
const spawnargs = browser.process().spawnargs; const spawnargs = browser.process()!.spawnargs;
if (!spawnargs) { if (!spawnargs) {
throw new Error('spawnargs not present'); throw new Error('spawnargs not present');
} }
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); expect(spawnargs.indexOf(defaultArgs[0]!)).toBe(-1);
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); expect(spawnargs.indexOf(defaultArgs[1]!)).not.toBe(-1);
expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); expect(spawnargs.indexOf(defaultArgs[2]!)).toBe(-1);
await browser.close(); await browser.close();
} }
); );
@ -444,22 +466,24 @@ describe('Launcher specs', function () {
const browser = await puppeteer.launch( const browser = await puppeteer.launch(
Object.assign({}, defaultBrowserOptions, { Object.assign({}, defaultBrowserOptions, {
// Only the first argument is fixed, others are optional. // Only the first argument is fixed, others are optional.
ignoreDefaultArgs: [defaultArgs[0]], ignoreDefaultArgs: [defaultArgs[0]!],
}) })
); );
const spawnargs = browser.process().spawnargs; const spawnargs = browser.process()!.spawnargs;
if (!spawnargs) { if (!spawnargs) {
throw new Error('spawnargs not present'); throw new Error('spawnargs not present');
} }
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); expect(spawnargs.indexOf(defaultArgs[0]!)).toBe(-1);
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); expect(spawnargs.indexOf(defaultArgs[1]!)).not.toBe(-1);
await browser.close(); await browser.close();
} }
); );
it('should have default URL when launching browser', async function () { it('should have default URL when launching browser', async function () {
const { defaultBrowserOptions, puppeteer } = getTestState(); const { defaultBrowserOptions, puppeteer } = getTestState();
const browser = await puppeteer.launch(defaultBrowserOptions); const browser = await puppeteer.launch(defaultBrowserOptions);
const pages = (await browser.pages()).map((page) => page.url()); const pages = (await browser.pages()).map((page) => {
return page.url();
});
expect(pages).toEqual(['about:blank']); expect(pages).toEqual(['about:blank']);
await browser.close(); await browser.close();
}); });
@ -473,7 +497,7 @@ describe('Launcher specs', function () {
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
const pages = await browser.pages(); const pages = await browser.pages();
expect(pages.length).toBe(1); expect(pages.length).toBe(1);
const page = pages[0]; const page = pages[0]!;
if (page.url() !== server.EMPTY_PAGE) { if (page.url() !== server.EMPTY_PAGE) {
await page.waitForNavigation(); await page.waitForNavigation();
} }
@ -486,8 +510,10 @@ describe('Launcher specs', function () {
const options = Object.assign({}, defaultBrowserOptions, { const options = Object.assign({}, defaultBrowserOptions, {
timeout: 1, timeout: 1,
}); });
let error = null; let error!: Error;
await puppeteer.launch(options).catch((error_) => (error = error_)); await puppeteer.launch(options).catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should set the default viewport', async () => { it('should set the default viewport', async () => {
@ -550,8 +576,10 @@ describe('Launcher specs', function () {
pipe: true, pipe: true,
}); });
let error = null; let error!: Error;
await puppeteer.launch(options).catch((error_) => (error = error_)); await puppeteer.launch(options).catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('either pipe or debugging port'); expect(error.message).toContain('either pipe or debugging port');
}); });
itChromeOnly( itChromeOnly(
@ -575,11 +603,11 @@ describe('Launcher specs', function () {
}); });
describe('Puppeteer.launch', function () { describe('Puppeteer.launch', function () {
let productName; let productName!: Product;
before(async () => { before(async () => {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
productName = puppeteer._productName; productName = puppeteer._productName!;
}); });
after(async () => { after(async () => {
@ -639,11 +667,19 @@ describe('Launcher specs', function () {
browserWSEndpoint: originalBrowser.wsEndpoint(), browserWSEndpoint: originalBrowser.wsEndpoint(),
}); });
const page = await otherBrowser.newPage(); const page = await otherBrowser.newPage();
expect(await page.evaluate(() => 7 * 8)).toBe(56); expect(
await page.evaluate(() => {
return 7 * 8;
})
).toBe(56);
otherBrowser.disconnect(); otherBrowser.disconnect();
const secondPage = await originalBrowser.newPage(); const secondPage = await originalBrowser.newPage();
expect(await secondPage.evaluate(() => 7 * 6)).toBe(42); expect(
await secondPage.evaluate(() => {
return 7 * 6;
})
).toBe(42);
await originalBrowser.close(); await originalBrowser.close();
}); });
it('should be able to close remote browser', async () => { it('should be able to close remote browser', async () => {
@ -670,16 +706,18 @@ describe('Launcher specs', function () {
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
}); });
const page = await browser.newPage(); const page = await browser.newPage();
let error = null; let error!: Error;
const [serverRequest, response] = await Promise.all([ const [serverRequest, response] = await Promise.all([
httpsServer.waitForRequest('/empty.html'), httpsServer.waitForRequest('/empty.html'),
page.goto(httpsServer.EMPTY_PAGE).catch((error_) => (error = error_)), page.goto(httpsServer.EMPTY_PAGE).catch((error_) => {
return (error = error_);
}),
]); ]);
expect(error).toBe(null); expect(error).toBeUndefined();
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.securityDetails()).toBeTruthy(); expect(response.securityDetails()).toBeTruthy();
const protocol = (serverRequest.socket as TLSSocket) const protocol = (serverRequest.socket as TLSSocket)
.getProtocol() .getProtocol()!
.replace('v', ' '); .replace('v', ' ');
expect(response.securityDetails().protocol()).toBe(protocol); expect(response.securityDetails().protocol()).toBe(protocol);
await page.close(); await page.close();
@ -700,8 +738,9 @@ describe('Launcher specs', function () {
const browser = await puppeteer.connect({ const browser = await puppeteer.connect({
browserWSEndpoint, browserWSEndpoint,
targetFilter: (targetInfo: Protocol.Target.TargetInfo) => targetFilter: (targetInfo: Protocol.Target.TargetInfo) => {
!targetInfo.url?.includes('should-be-ignored'), return !targetInfo.url?.includes('should-be-ignored');
},
}); });
const pages = await browser.pages(); const pages = await browser.pages();
@ -711,10 +750,13 @@ describe('Launcher specs', function () {
await browser.disconnect(); await browser.disconnect();
await originalBrowser.close(); await originalBrowser.close();
expect(pages.map((p: Page) => p.url()).sort()).toEqual([ expect(
'about:blank', pages
server.EMPTY_PAGE, .map((p: Page) => {
]); return p.url();
})
.sort()
).toEqual(['about:blank', server.EMPTY_PAGE]);
}); });
itFailsFirefox( itFailsFirefox(
'should be able to reconnect to a disconnected browser', 'should be able to reconnect to a disconnected browser',
@ -729,10 +771,9 @@ describe('Launcher specs', function () {
const browser = await puppeteer.connect({ browserWSEndpoint }); const browser = await puppeteer.connect({ browserWSEndpoint });
const pages = await browser.pages(); const pages = await browser.pages();
const restoredPage = pages.find( const restoredPage = pages.find((page) => {
(page) => return page.url() === server.PREFIX + '/frames/nested-frames.html';
page.url() === server.PREFIX + '/frames/nested-frames.html' })!;
);
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
'http://localhost:<PORT>/frames/nested-frames.html', 'http://localhost:<PORT>/frames/nested-frames.html',
' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/two-frames.html (2frames)',
@ -740,7 +781,11 @@ describe('Launcher specs', function () {
' http://localhost:<PORT>/frames/frame.html (dos)', ' http://localhost:<PORT>/frames/frame.html (dos)',
' http://localhost:<PORT>/frames/frame.html (aframe)', ' http://localhost:<PORT>/frames/frame.html (aframe)',
]); ]);
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); expect(
await restoredPage.evaluate(() => {
return 7 * 8;
})
).toBe(56);
await browser.close(); await browser.close();
} }
); );
@ -755,13 +800,23 @@ describe('Launcher specs', function () {
browserWSEndpoint: browserOne.wsEndpoint(), browserWSEndpoint: browserOne.wsEndpoint(),
}); });
const [page1, page2] = await Promise.all([ const [page1, page2] = await Promise.all([
new Promise<Page>((x) => new Promise<Page>((x) => {
browserOne.once('targetcreated', (target) => x(target.page())) return browserOne.once('targetcreated', (target) => {
), return x(target.page());
});
}),
browserTwo.newPage(), browserTwo.newPage(),
]); ]);
expect(await page1.evaluate(() => 7 * 8)).toBe(56); expect(
expect(await page2.evaluate(() => 7 * 6)).toBe(42); await page1.evaluate(() => {
return 7 * 8;
})
).toBe(56);
expect(
await page2.evaluate(() => {
return 7 * 6;
})
).toBe(42);
await browserOne.close(); await browserOne.close();
} }
); );
@ -775,12 +830,14 @@ describe('Launcher specs', function () {
const browserTwo = await puppeteer.connect({ browserWSEndpoint }); const browserTwo = await puppeteer.connect({ browserWSEndpoint });
const pages = await browserTwo.pages(); const pages = await browserTwo.pages();
const pageTwo = pages.find((page) => page.url() === server.EMPTY_PAGE); const pageTwo = pages.find((page) => {
return page.url() === server.EMPTY_PAGE;
})!;
await pageTwo.reload(); await pageTwo.reload();
const bodyHandle = await pageTwo.waitForSelector('body', { const bodyHandle = await pageTwo.waitForSelector('body', {
timeout: 10000, timeout: 10000,
}); });
await bodyHandle.dispose(); await bodyHandle!.dispose();
await browserTwo.close(); await browserTwo.close();
}); });
}); });
@ -802,13 +859,15 @@ describe('Launcher specs', function () {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
beforeEach(() => { beforeEach(() => {
process.env.PUPPETEER_EXECUTABLE_PATH = ''; process.env['PUPPETEER_EXECUTABLE_PATH'] = '';
sandbox sandbox
.stub(process.env, 'PUPPETEER_EXECUTABLE_PATH') .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
.value('SOME_CUSTOM_EXECUTABLE'); .value('SOME_CUSTOM_EXECUTABLE');
}); });
afterEach(() => sandbox.restore()); afterEach(() => {
return sandbox.restore();
});
it('its value is returned', async () => { it('its value is returned', async () => {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
@ -840,13 +899,15 @@ describe('Launcher specs', function () {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
beforeEach(() => { beforeEach(() => {
process.env.PUPPETEER_EXECUTABLE_PATH = ''; process.env['PUPPETEER_EXECUTABLE_PATH'] = '';
sandbox sandbox
.stub(process.env, 'PUPPETEER_EXECUTABLE_PATH') .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
.value('SOME_CUSTOM_EXECUTABLE'); .value('SOME_CUSTOM_EXECUTABLE');
}); });
afterEach(() => sandbox.restore()); afterEach(() => {
return sandbox.restore();
});
it('its value is returned', async () => { it('its value is returned', async () => {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
@ -888,10 +949,16 @@ describe('Launcher specs', function () {
const { server, puppeteer, defaultBrowserOptions } = getTestState(); const { server, puppeteer, defaultBrowserOptions } = getTestState();
const browser = await puppeteer.launch(defaultBrowserOptions); const browser = await puppeteer.launch(defaultBrowserOptions);
const events = []; const events: string[] = [];
browser.on('targetcreated', () => events.push('CREATED')); browser.on('targetcreated', () => {
browser.on('targetchanged', () => events.push('CHANGED')); return events.push('CREATED');
browser.on('targetdestroyed', () => events.push('DESTROYED')); });
browser.on('targetchanged', () => {
return events.push('CHANGED');
});
browser.on('targetdestroyed', () => {
return events.push('DESTROYED');
});
const page = await browser.newPage(); const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.close(); await page.close();
@ -911,9 +978,15 @@ describe('Launcher specs', function () {
let disconnectedOriginal = 0; let disconnectedOriginal = 0;
let disconnectedRemote1 = 0; let disconnectedRemote1 = 0;
let disconnectedRemote2 = 0; let disconnectedRemote2 = 0;
originalBrowser.on('disconnected', () => ++disconnectedOriginal); originalBrowser.on('disconnected', () => {
remoteBrowser1.on('disconnected', () => ++disconnectedRemote1); return ++disconnectedOriginal;
remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); });
remoteBrowser1.on('disconnected', () => {
return ++disconnectedRemote1;
});
remoteBrowser2.on('disconnected', () => {
return ++disconnectedRemote2;
});
await Promise.all([ await Promise.all([
utils.waitEvent(remoteBrowser2, 'disconnected'), utils.waitEvent(remoteBrowser2, 'disconnected'),

View File

@ -25,12 +25,15 @@ import {
Browser, Browser,
BrowserContext, BrowserContext,
} from '../../lib/cjs/puppeteer/common/Browser.js'; } from '../../lib/cjs/puppeteer/common/Browser.js';
import { isErrorLike } from '../../lib/cjs/puppeteer/common/util.js';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js'; import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { PuppeteerNode } from '../../lib/cjs/puppeteer/node/Puppeteer.js'; import { isErrorLike } from '../../lib/cjs/puppeteer/common/util.js';
import {
PuppeteerLaunchOptions,
PuppeteerNode,
} from '../../lib/cjs/puppeteer/node/Puppeteer.js';
import puppeteer from '../../lib/cjs/puppeteer/puppeteer.js'; import puppeteer from '../../lib/cjs/puppeteer/puppeteer.js';
import utils from './utils.js';
import { TestServer } from '../../utils/testserver/lib/index.js'; import { TestServer } from '../../utils/testserver/lib/index.js';
import { extendExpectWithToBeGolden } from './utils.js';
const setupServer = async () => { const setupServer = async () => {
const assetsPath = path.join(__dirname, '../assets'); const assetsPath = path.join(__dirname, '../assets');
@ -55,8 +58,9 @@ const setupServer = async () => {
return { server, httpsServer }; return { server, httpsServer };
}; };
export const getTestState = (): PuppeteerTestState => export const getTestState = (): PuppeteerTestState => {
state as PuppeteerTestState; return state as PuppeteerTestState;
};
const product = const product =
process.env['PRODUCT'] || process.env['PUPPETEER_PRODUCT'] || 'Chromium'; process.env['PRODUCT'] || process.env['PUPPETEER_PRODUCT'] || 'Chromium';
@ -127,7 +131,7 @@ const setupGoldenAssertions = (): void => {
if (fs.existsSync(OUTPUT_DIR)) { if (fs.existsSync(OUTPUT_DIR)) {
rimraf.sync(OUTPUT_DIR); rimraf.sync(OUTPUT_DIR);
} }
utils.extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR); extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR);
}; };
setupGoldenAssertions(); setupGoldenAssertions();
@ -137,9 +141,7 @@ interface PuppeteerTestState {
context: BrowserContext; context: BrowserContext;
page: Page; page: Page;
puppeteer: PuppeteerNode; puppeteer: PuppeteerNode;
defaultBrowserOptions: { defaultBrowserOptions: PuppeteerLaunchOptions;
[x: string]: any;
};
server: TestServer; server: TestServer;
httpsServer: TestServer; httpsServer: TestServer;
isFirefox: boolean; isFirefox: boolean;
@ -304,16 +306,16 @@ export const mochaHooks = {
], ],
beforeEach: async (): Promise<void> => { beforeEach: async (): Promise<void> => {
state.server.reset(); state.server!.reset();
state.httpsServer.reset(); state.httpsServer!.reset();
}, },
afterAll: [ afterAll: [
async (): Promise<void> => { async (): Promise<void> => {
await state.server.stop(); await state.server!.stop();
state.server = null; state.server = undefined;
await state.httpsServer.stop(); await state.httpsServer!.stop();
state.httpsServer = null; state.httpsServer = undefined;
}, },
], ],
@ -357,6 +359,8 @@ export const shortWaitForArrayToHaveAtLeastNElements = async (
if (data.length >= minLength) { if (data.length >= minLength) {
break; break;
} }
await new Promise((resolve) => setTimeout(resolve, timeout)); await new Promise((resolve) => {
return setTimeout(resolve, timeout);
});
} }
}; };

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { KeyInput } from '../../lib/cjs/puppeteer/common/USKeyboardLayout.js'; import { KeyInput } from '../../lib/cjs/puppeteer/common/USKeyboardLayout.js';
interface Dimensions { interface Dimensions {
@ -31,7 +31,7 @@ interface Dimensions {
} }
function dimensions(): Dimensions { function dimensions(): Dimensions {
const rect = document.querySelector('textarea').getBoundingClientRect(); const rect = document.querySelector('textarea')!.getBoundingClientRect();
return { return {
x: rect.left, x: rect.left,
y: rect.top, y: rect.top,
@ -47,7 +47,7 @@ describe('Mouse', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluate(() => { await page.evaluate(() => {
globalThis.clickPromise = new Promise((resolve) => { (globalThis as any).clickPromise = new Promise((resolve) => {
document.addEventListener('click', (event) => { document.addEventListener('click', (event) => {
resolve({ resolve({
type: event.type, type: event.type,
@ -61,9 +61,9 @@ describe('Mouse', function () {
}); });
}); });
await page.mouse.click(50, 60); await page.mouse.click(50, 60);
const event = await page.evaluate<() => MouseEvent>( const event = await page.evaluate<() => MouseEvent>(() => {
() => globalThis.clickPromise return (globalThis as any).clickPromise;
); });
expect(event.type).toBe('click'); expect(event.type).toBe('click');
expect(event.detail).toBe(1); expect(event.detail).toBe(1);
expect(event.clientX).toBe(50); expect(event.clientX).toBe(50);
@ -96,10 +96,12 @@ describe('Mouse', function () {
"This is the text that we are going to try to select. Let's see how it goes."; "This is the text that we are going to try to select. Let's see how it goes.";
await page.keyboard.type(text); await page.keyboard.type(text);
// Firefox needs an extra frame here after typing or it will fail to set the scrollTop // Firefox needs an extra frame here after typing or it will fail to set the scrollTop
await page.evaluate(() => new Promise(requestAnimationFrame)); await page.evaluate(() => {
await page.evaluate( return new Promise(requestAnimationFrame);
() => (document.querySelector('textarea').scrollTop = 0) });
); await page.evaluate(() => {
return (document.querySelector('textarea')!.scrollTop = 0);
});
const { x, y } = await page.evaluate(dimensions); const { x, y } = await page.evaluate(dimensions);
await page.mouse.move(x + 2, y + 2); await page.mouse.move(x + 2, y + 2);
await page.mouse.down(); await page.mouse.down();
@ -107,7 +109,7 @@ describe('Mouse', function () {
await page.mouse.up(); await page.mouse.up();
expect( expect(
await page.evaluate(() => { await page.evaluate(() => {
const textarea = document.querySelector('textarea'); const textarea = document.querySelector('textarea')!;
return textarea.value.substring( return textarea.value.substring(
textarea.selectionStart, textarea.selectionStart,
textarea.selectionEnd textarea.selectionEnd
@ -121,15 +123,21 @@ describe('Mouse', function () {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.hover('#button-6'); await page.hover('#button-6');
expect( expect(
await page.evaluate(() => document.querySelector('button:hover').id) await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6'); ).toBe('button-6');
await page.hover('#button-2'); await page.hover('#button-2');
expect( expect(
await page.evaluate(() => document.querySelector('button:hover').id) await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-2'); ).toBe('button-2');
await page.hover('#button-91'); await page.hover('#button-91');
expect( expect(
await page.evaluate(() => document.querySelector('button:hover').id) await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-91'); ).toBe('button-91');
}); });
itFailsFirefox( itFailsFirefox(
@ -138,10 +146,15 @@ describe('Mouse', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window.Node); await page.evaluate(() => {
// @ts-expect-error Expected.
return delete window.Node;
});
await page.hover('#button-6'); await page.hover('#button-6');
expect( expect(
await page.evaluate(() => document.querySelector('button:hover').id) await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6'); ).toBe('button-6');
} }
); );
@ -149,11 +162,15 @@ describe('Mouse', function () {
const { page, server, isFirefox } = getTestState(); const { page, server, isFirefox } = getTestState();
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => await page.evaluate(() => {
document return document.querySelector('#button-3')!.addEventListener(
.querySelector('#button-3') 'mousedown',
.addEventListener('mousedown', (e) => (globalThis.lastEvent = e), true) (e) => {
return ((globalThis as any).lastEvent = e);
},
true
); );
});
const modifiers = new Map<KeyInput, string>([ const modifiers = new Map<KeyInput, string>([
['Shift', 'shiftKey'], ['Shift', 'shiftKey'],
['Control', 'ctrlKey'], ['Control', 'ctrlKey'],
@ -162,13 +179,15 @@ describe('Mouse', function () {
]); ]);
// In Firefox, the Meta modifier only exists on Mac // In Firefox, the Meta modifier only exists on Mac
if (isFirefox && os.platform() !== 'darwin') { if (isFirefox && os.platform() !== 'darwin') {
delete modifiers['Meta']; modifiers.delete('Meta');
} }
for (const [modifier, key] of modifiers) { for (const [modifier, key] of modifiers) {
await page.keyboard.down(modifier); await page.keyboard.down(modifier);
await page.click('#button-3'); await page.click('#button-3');
if ( if (
!(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key)) !(await page.evaluate((mod: string) => {
return (globalThis as any).lastEvent[mod];
}, key))
) { ) {
throw new Error(key + ' should be true'); throw new Error(key + ' should be true');
} }
@ -177,9 +196,11 @@ describe('Mouse', function () {
await page.click('#button-3'); await page.click('#button-3');
for (const [modifier, key] of modifiers) { for (const [modifier, key] of modifiers) {
if ( if (
await page.evaluate((mod: string) => globalThis.lastEvent[mod], key) await page.evaluate((mod: string) => {
return (globalThis as any).lastEvent[mod];
}, key)
) { ) {
throw new Error(modifiers[modifier] + ' should be false'); throw new Error(modifiers.get(modifier) + ' should be false');
} }
} }
}); });
@ -187,8 +208,8 @@ describe('Mouse', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/wheel.html'); await page.goto(server.PREFIX + '/input/wheel.html');
const elem = await page.$('div'); const elem = (await page.$('div'))!;
const boundingBoxBefore = await elem.boundingBox(); const boundingBoxBefore = (await elem.boundingBox())!;
expect(boundingBoxBefore).toMatchObject({ expect(boundingBoxBefore).toMatchObject({
width: 115, width: 115,
height: 115, height: 115,
@ -211,9 +232,9 @@ describe('Mouse', function () {
await page.mouse.move(100, 100); await page.mouse.move(100, 100);
await page.evaluate(() => { await page.evaluate(() => {
globalThis.result = []; (globalThis as any).result = [];
document.addEventListener('mousemove', (event) => { document.addEventListener('mousemove', (event) => {
globalThis.result.push([event.clientX, event.clientY]); (globalThis as any).result.push([event.clientX, event.clientY]);
}); });
}); });
await page.mouse.move(200, 300, { steps: 5 }); await page.mouse.move(200, 300, { steps: 5 });
@ -234,7 +255,7 @@ describe('Mouse', function () {
await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html'); await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html');
await page.evaluate(() => { await page.evaluate(() => {
document.addEventListener('click', (event) => { document.addEventListener('click', (event) => {
globalThis.result = { x: event.clientX, y: event.clientY }; (globalThis as any).result = { x: event.clientX, y: event.clientY };
}); });
}); });

View File

@ -22,8 +22,10 @@ import {
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import os from 'os'; import os from 'os';
import { ServerResponse } from 'http';
import { HTTPRequest } from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
describe('navigation', function () { describe('navigation', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -62,31 +64,35 @@ describe('navigation', function () {
it('should return response when page changes its URL after load', async () => { it('should return response when page changes its URL after load', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/historyapi.html'); const response = (await page.goto(server.PREFIX + '/historyapi.html'))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
itFailsFirefox('should work with subframes return 204', async () => { itFailsFirefox('should work with subframes return 204', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/frames/frame.html', (req, res) => { server.setRoute('/frames/frame.html', (_req, res) => {
res.statusCode = 204; res.statusCode = 204;
res.end(); res.end();
}); });
let error = null; let error!: Error;
await page await page
.goto(server.PREFIX + '/frames/one-frame.html') .goto(server.PREFIX + '/frames/one-frame.html')
.catch((error_) => (error = error_)); .catch((error_) => {
expect(error).toBe(null); return (error = error_);
});
expect(error).toBeUndefined();
}); });
itFailsFirefox('should fail when server returns 204', async () => { itFailsFirefox('should fail when server returns 204', async () => {
const { page, server, isChrome } = getTestState(); const { page, server, isChrome } = getTestState();
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.statusCode = 204; res.statusCode = 204;
res.end(); res.end();
}); });
let error = null; let error!: Error;
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); await page.goto(server.EMPTY_PAGE).catch((error_) => {
return (error = error_);
});
expect(error).not.toBe(null); expect(error).not.toBe(null);
if (isChrome) { if (isChrome) {
expect(error.message).toContain('net::ERR_ABORTED'); expect(error.message).toContain('net::ERR_ABORTED');
@ -100,7 +106,7 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, { const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'domcontentloaded', waitUntil: 'domcontentloaded',
}); });
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
}); });
it('should work when page calls history API in beforeunload', async () => { it('should work when page calls history API in beforeunload', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -109,12 +115,14 @@ describe('navigation', function () {
await page.evaluate(() => { await page.evaluate(() => {
window.addEventListener( window.addEventListener(
'beforeunload', 'beforeunload',
() => history.replaceState(null, 'initial', window.location.href), () => {
return history.replaceState(null, 'initial', window.location.href);
},
false false
); );
}); });
const response = await page.goto(server.PREFIX + '/grid.html'); const response = await page.goto(server.PREFIX + '/grid.html');
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
}); });
itFailsFirefox( itFailsFirefox(
'should navigate to empty page with networkidle0', 'should navigate to empty page with networkidle0',
@ -124,7 +132,7 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, { const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'networkidle0', waitUntil: 'networkidle0',
}); });
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
} }
); );
itFailsFirefox( itFailsFirefox(
@ -135,14 +143,16 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, { const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'networkidle2', waitUntil: 'networkidle2',
}); });
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
} }
); );
itFailsFirefox('should fail when navigating to bad url', async () => { itFailsFirefox('should fail when navigating to bad url', async () => {
const { page, isChrome } = getTestState(); const { page, isChrome } = getTestState();
let error = null; let error!: Error;
await page.goto('asdfasdf').catch((error_) => (error = error_)); await page.goto('asdfasdf').catch((error_) => {
return (error = error_);
});
if (isChrome) { if (isChrome) {
expect(error.message).toContain('Cannot navigate to invalid URL'); expect(error.message).toContain('Cannot navigate to invalid URL');
} else { } else {
@ -165,15 +175,21 @@ describe('navigation', function () {
// Make sure that network events do not emit 'undefined'. // Make sure that network events do not emit 'undefined'.
// @see https://crbug.com/750469 // @see https://crbug.com/750469
const requests = []; const requests: string[] = [];
page.on('request', () => requests.push('request')); page.on('request', () => {
page.on('requestfinished', () => requests.push('requestfinished')); return requests.push('request');
page.on('requestfailed', () => requests.push('requestfailed')); });
page.on('requestfinished', () => {
return requests.push('requestfinished');
});
page.on('requestfailed', () => {
return requests.push('requestfailed');
});
let error = null; let error!: Error;
await page await page.goto(httpsServer.EMPTY_PAGE).catch((error_) => {
.goto(httpsServer.EMPTY_PAGE) return (error = error_);
.catch((error_) => (error = error_)); });
if (isChrome) { if (isChrome) {
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE); expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
} else { } else {
@ -181,18 +197,20 @@ describe('navigation', function () {
} }
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
expect(requests[0]).toBe('request'); expect(requests[0]!).toBe('request');
expect(requests[1]).toBe('requestfailed'); expect(requests[1]!).toBe('requestfailed');
}); });
it('should fail when navigating to bad SSL after redirects', async () => { it('should fail when navigating to bad SSL after redirects', async () => {
const { page, server, httpsServer, isChrome } = getTestState(); const { page, server, httpsServer, isChrome } = getTestState();
server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/empty.html'); server.setRedirect('/redirect/2.html', '/empty.html');
let error = null; let error!: Error;
await page await page
.goto(httpsServer.PREFIX + '/redirect/1.html') .goto(httpsServer.PREFIX + '/redirect/1.html')
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
if (isChrome) { if (isChrome) {
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE); expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
} else { } else {
@ -202,11 +220,13 @@ describe('navigation', function () {
it('should throw if networkidle is passed as an option', async () => { it('should throw if networkidle is passed as an option', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error = null; let error!: Error;
await page await page
// @ts-expect-error purposefully passing an old option // @ts-expect-error purposefully passing an old option
.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }) .goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toContain( expect(error.message).toContain(
'"networkidle" option is no longer supported' '"networkidle" option is no longer supported'
); );
@ -214,10 +234,12 @@ describe('navigation', function () {
it('should fail when main resources failed to load', async () => { it('should fail when main resources failed to load', async () => {
const { page, isChrome } = getTestState(); const { page, isChrome } = getTestState();
let error = null; let error!: Error;
await page await page
.goto('http://localhost:44123/non-existing-url') .goto('http://localhost:44123/non-existing-url')
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
if (isChrome) { if (isChrome) {
expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
} else { } else {
@ -229,10 +251,12 @@ describe('navigation', function () {
// Hang for request to the empty.html // Hang for request to the empty.html
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
let error = null; let error!: Error;
await page await page
.goto(server.PREFIX + '/empty.html', { timeout: 1 }) .goto(server.PREFIX + '/empty.html', { timeout: 1 })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
@ -241,11 +265,11 @@ describe('navigation', function () {
// Hang for request to the empty.html // Hang for request to the empty.html
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
let error = null; let error!: Error;
page.setDefaultNavigationTimeout(1); page.setDefaultNavigationTimeout(1);
await page await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
.goto(server.PREFIX + '/empty.html') return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
@ -254,11 +278,11 @@ describe('navigation', function () {
// Hang for request to the empty.html // Hang for request to the empty.html
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
let error = null; let error!: Error;
page.setDefaultTimeout(1); page.setDefaultTimeout(1);
await page await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
.goto(server.PREFIX + '/empty.html') return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
@ -267,43 +291,47 @@ describe('navigation', function () {
// Hang for request to the empty.html // Hang for request to the empty.html
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
let error = null; let error!: Error;
page.setDefaultTimeout(0); page.setDefaultTimeout(0);
page.setDefaultNavigationTimeout(1); page.setDefaultNavigationTimeout(1);
await page await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
.goto(server.PREFIX + '/empty.html') return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should disable timeout when its set to 0', async () => { it('should disable timeout when its set to 0', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error = null; let error!: Error;
let loaded = false; let loaded = false;
page.once('load', () => (loaded = true)); page.once('load', () => {
return (loaded = true);
});
await page await page
.goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] }) .goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] })
.catch((error_) => (error = error_)); .catch((error_) => {
expect(error).toBe(null); return (error = error_);
});
expect(error).toBeUndefined();
expect(loaded).toBe(true); expect(loaded).toBe(true);
}); });
it('should work when navigating to valid url', async () => { it('should work when navigating to valid url', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
itFailsFirefox('should work when navigating to data url', async () => { itFailsFirefox('should work when navigating to data url', async () => {
const { page } = getTestState(); const { page } = getTestState();
const response = await page.goto('data:text/html,hello'); const response = (await page.goto('data:text/html,hello'))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
it('should work when navigating to 404', async () => { it('should work when navigating to 404', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/not-found'); const response = (await page.goto(server.PREFIX + '/not-found'))!;
expect(response.ok()).toBe(false); expect(response.ok()).toBe(false);
expect(response.status()).toBe(404); expect(response.status()).toBe(404);
}); });
@ -313,7 +341,7 @@ describe('navigation', function () {
server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/redirect/3.html'); server.setRedirect('/redirect/2.html', '/redirect/3.html');
server.setRedirect('/redirect/3.html', server.EMPTY_PAGE); server.setRedirect('/redirect/3.html', server.EMPTY_PAGE);
const response = await page.goto(server.PREFIX + '/redirect/1.html'); const response = (await page.goto(server.PREFIX + '/redirect/1.html'))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);
}); });
@ -322,20 +350,20 @@ describe('navigation', function () {
async () => { async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let responses = []; let responses: ServerResponse[] = [];
// Hold on to a bunch of requests without answering. // Hold on to a bunch of requests without answering.
server.setRoute('/fetch-request-a.js', (req, res) => server.setRoute('/fetch-request-a.js', (_req, res) => {
responses.push(res) return responses.push(res);
); });
server.setRoute('/fetch-request-b.js', (req, res) => server.setRoute('/fetch-request-b.js', (_req, res) => {
responses.push(res) return responses.push(res);
); });
server.setRoute('/fetch-request-c.js', (req, res) => server.setRoute('/fetch-request-c.js', (_req, res) => {
responses.push(res) return responses.push(res);
); });
server.setRoute('/fetch-request-d.js', (req, res) => server.setRoute('/fetch-request-d.js', (_req, res) => {
responses.push(res) return responses.push(res);
); });
const initialFetchResourcesRequested = Promise.all([ const initialFetchResourcesRequested = Promise.all([
server.waitForRequest('/fetch-request-a.js'), server.waitForRequest('/fetch-request-a.js'),
server.waitForRequest('/fetch-request-b.js'), server.waitForRequest('/fetch-request-b.js'),
@ -355,10 +383,14 @@ describe('navigation', function () {
); );
// Track when the navigation gets completed. // Track when the navigation gets completed.
let navigationFinished = false; let navigationFinished = false;
navigationPromise.then(() => (navigationFinished = true)); navigationPromise.then(() => {
return (navigationFinished = true);
});
// Wait for the page's 'load' event. // Wait for the page's 'load' event.
await new Promise((fulfill) => page.once('load', fulfill)); await new Promise((fulfill) => {
return page.once('load', fulfill);
});
expect(navigationFinished).toBe(false); expect(navigationFinished).toBe(false);
// Wait for the initial three resources to be requested. // Wait for the initial three resources to be requested.
@ -387,7 +419,7 @@ describe('navigation', function () {
response.end(`File not found`); response.end(`File not found`);
} }
const response = await navigationPromise; const response = (await navigationPromise)!;
// Expect navigation to succeed. // Expect navigation to succeed.
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
} }
@ -396,7 +428,9 @@ describe('navigation', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
let warning = null; let warning = null;
const warningHandler = (w) => (warning = w); const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler); process.on('warning', warningHandler);
for (let i = 0; i < 20; ++i) { for (let i = 0; i < 20; ++i) {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -408,7 +442,9 @@ describe('navigation', function () {
const { page } = getTestState(); const { page } = getTestState();
let warning = null; let warning = null;
const warningHandler = (w) => (warning = w); const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler); process.on('warning', warningHandler);
for (let i = 0; i < 20; ++i) { for (let i = 0; i < 20; ++i) {
await page.goto('asdf').catch(() => { await page.goto('asdf').catch(() => {
@ -422,7 +458,9 @@ describe('navigation', function () {
const { context, server } = getTestState(); const { context, server } = getTestState();
let warning = null; let warning = null;
const warningHandler = (w) => (warning = w); const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler); process.on('warning', warningHandler);
await Promise.all( await Promise.all(
[...Array(20)].map(async () => { [...Array(20)].map(async () => {
@ -439,16 +477,15 @@ describe('navigation', function () {
async () => { async () => {
const { page } = getTestState(); const { page } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
);
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const response = await page.goto(dataURL); const response = (await page.goto(dataURL))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL); expect(requests[0]!.url()).toBe(dataURL);
} }
); );
itFailsFirefox( itFailsFirefox(
@ -456,22 +493,21 @@ describe('navigation', function () {
async () => { async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); const response = (await page.goto(server.EMPTY_PAGE + '#hash'))!;
const response = await page.goto(server.EMPTY_PAGE + '#hash');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
} }
); );
it('should work with self requesting page', async () => { it('should work with self requesting page', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/self-request.html'); const response = (await page.goto(server.PREFIX + '/self-request.html'))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toContain('self-request.html'); expect(response.url()).toContain('self-request.html');
}); });
@ -479,11 +515,11 @@ describe('navigation', function () {
const { page, httpsServer } = getTestState(); const { page, httpsServer } = getTestState();
const url = httpsServer.PREFIX + '/redirect/1.html'; const url = httpsServer.PREFIX + '/redirect/1.html';
let error = null; let error!: Error;
try { try {
await page.goto(url); await page.goto(url);
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toContain(url); expect(error.message).toContain(url);
}); });
@ -510,19 +546,20 @@ describe('navigation', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([ const [response] = await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.evaluate( page.evaluate((url: string) => {
(url: string) => (window.location.href = url), return (window.location.href = url);
server.PREFIX + '/grid.html' }, server.PREFIX + '/grid.html'),
),
]); ]);
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
expect(response.url()).toContain('grid.html'); expect(response!.url()).toContain('grid.html');
}); });
it('should work with both domcontentloaded and load', async () => { it('should work with both domcontentloaded and load', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let response = null; let response!: ServerResponse;
server.setRoute('/one-style.css', (req, res) => (response = res)); server.setRoute('/one-style.css', (_req, res) => {
return (response = res);
});
const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
const domContentLoadedPromise = page.waitForNavigation({ const domContentLoadedPromise = page.waitForNavigation({
waitUntil: 'domcontentloaded', waitUntil: 'domcontentloaded',
@ -533,7 +570,9 @@ describe('navigation', function () {
.waitForNavigation({ .waitForNavigation({
waitUntil: ['load', 'domcontentloaded'], waitUntil: ['load', 'domcontentloaded'],
}) })
.then(() => (bothFired = true)); .then(() => {
return (bothFired = true);
});
await server.waitForRequest('/one-style.css'); await server.waitForRequest('/one-style.css');
await domContentLoadedPromise; await domContentLoadedPromise;
@ -637,7 +676,9 @@ describe('navigation', function () {
}); });
}); });
await Promise.all([ await Promise.all([
frame.evaluate(() => window.stop()), frame.evaluate(() => {
return window.stop();
}),
navigationPromise, navigationPromise,
]); ]);
} }
@ -651,15 +692,15 @@ describe('navigation', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
let response = await page.goBack(); let response = (await page.goBack())!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.url()).toContain(server.EMPTY_PAGE); expect(response.url()).toContain(server.EMPTY_PAGE);
response = await page.goForward(); response = (await page.goForward())!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.url()).toContain('/grid.html'); expect(response.url()).toContain('/grid.html');
response = await page.goForward(); response = (await page.goForward())!;
expect(response).toBe(null); expect(response).toBe(null);
}); });
itFailsFirefox('should work with HistoryAPI', async () => { itFailsFirefox('should work with HistoryAPI', async () => {
@ -686,12 +727,12 @@ describe('navigation', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); expect(page.frames()[0]!.url()).toContain('/frames/one-frame.html');
expect(page.frames()[1].url()).toContain('/frames/frame.html'); expect(page.frames()[1]!.url()).toContain('/frames/frame.html');
const response = await page.frames()[1].goto(server.EMPTY_PAGE); const response = (await page.frames()[1]!.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.frame()).toBe(page.frames()[1]); expect(response.frame()).toBe(page.frames()[1]!);
}); });
it('should reject when frame detaches', async () => { it('should reject when frame detaches', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -700,12 +741,16 @@ describe('navigation', function () {
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
const navigationPromise = page const navigationPromise = page
.frames()[1] .frames()[1]!
.goto(server.EMPTY_PAGE) .goto(server.EMPTY_PAGE)
.catch((error_) => error_); .catch((error_) => {
return error_;
});
await server.waitForRequest('/empty.html'); await server.waitForRequest('/empty.html');
await page.$eval('iframe', (frame) => frame.remove()); await page.$eval('iframe', (frame) => {
return frame.remove();
});
const error = await navigationPromise; const error = await navigationPromise;
expect(error.message).toBe('Navigating frame was detached'); expect(error.message).toBe('Navigating frame was detached');
}); });
@ -722,20 +767,20 @@ describe('navigation', function () {
utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), utils.attachFrame(page, 'frame3', server.EMPTY_PAGE),
]); ]);
// Navigate all frames to the same URL. // Navigate all frames to the same URL.
const serverResponses = []; const serverResponses: ServerResponse[] = [];
server.setRoute('/one-style.html', (req, res) => server.setRoute('/one-style.html', (_req, res) => {
serverResponses.push(res) return serverResponses.push(res);
); });
const navigations = []; const navigations = [];
for (let i = 0; i < 3; ++i) { for (let i = 0; i < 3; ++i) {
navigations.push(frames[i].goto(server.PREFIX + '/one-style.html')); navigations.push(frames[i]!.goto(server.PREFIX + '/one-style.html'));
await server.waitForRequest('/one-style.html'); await server.waitForRequest('/one-style.html');
} }
// Respond from server out-of-order. // Respond from server out-of-order.
const serverResponseTexts = ['AAA', 'BBB', 'CCC']; const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
for (const i of [1, 2, 0]) { for (const i of [1, 2, 0]) {
serverResponses[i].end(serverResponseTexts[i]); serverResponses[i]!.end(serverResponseTexts[i]);
const response = await navigations[i]; const response = (await navigations[i])!;
expect(response.frame()).toBe(frames[i]); expect(response.frame()).toBe(frames[i]);
expect(await response.text()).toBe(serverResponseTexts[i]); expect(await response.text()).toBe(serverResponseTexts[i]);
} }
@ -747,35 +792,38 @@ describe('navigation', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1]; const frame = page.frames()[1]!;
const [response] = await Promise.all([ const [response] = await Promise.all([
frame.waitForNavigation(), frame.waitForNavigation(),
frame.evaluate( frame.evaluate((url: string) => {
(url: string) => (window.location.href = url), return (window.location.href = url);
server.PREFIX + '/grid.html' }, server.PREFIX + '/grid.html'),
),
]); ]);
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
expect(response.url()).toContain('grid.html'); expect(response!.url()).toContain('grid.html');
expect(response.frame()).toBe(frame); expect(response!.frame()).toBe(frame);
expect(page.url()).toContain('/frames/one-frame.html'); expect(page.url()).toContain('/frames/one-frame.html');
}); });
it('should fail when frame detaches', async () => { it('should fail when frame detaches', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1]; const frame = page.frames()[1]!;
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
let error = null; let error!: Error;
const navigationPromise = frame const navigationPromise = frame.waitForNavigation().catch((error_) => {
.waitForNavigation() return (error = error_);
.catch((error_) => (error = error_)); });
await Promise.all([ await Promise.all([
server.waitForRequest('/empty.html'), server.waitForRequest('/empty.html'),
frame.evaluate(() => ((window as any).location = '/empty.html')), frame.evaluate(() => {
return ((window as any).location = '/empty.html');
}),
]); ]);
await page.$eval('iframe', (frame) => frame.remove()); await page.$eval('iframe', (frame) => {
return frame.remove();
});
await navigationPromise; await navigationPromise;
expect(error.message).toBe('Navigating frame was detached'); expect(error.message).toBe('Navigating frame was detached');
}); });
@ -786,9 +834,15 @@ describe('navigation', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => (globalThis._foo = 10)); await page.evaluate(() => {
return ((globalThis as any)._foo = 10);
});
await page.reload(); await page.reload();
expect(await page.evaluate(() => globalThis._foo)).toBe(undefined); expect(
await page.evaluate(() => {
return (globalThis as any)._foo;
})
).toBe(undefined);
}); });
}); });
}); });

View File

@ -26,8 +26,10 @@ import {
describeFailsFirefox, describeFailsFirefox,
itChromeOnly, itChromeOnly,
itFirefoxOnly, itFirefoxOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { HTTPResponse } from '../../lib/cjs/puppeteer/api-docs-entry.js'; import { HTTPRequest } from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
import { HTTPResponse } from '../../lib/cjs/puppeteer/common/HTTPResponse.js';
import { ServerResponse } from 'http';
describe('network', function () { describe('network', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -37,36 +39,35 @@ describe('network', function () {
it('should fire for navigation requests', async () => { it('should fire for navigation requests', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); (await page.goto(server.EMPTY_PAGE))!;
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
}); });
it('should fire for iframes', async () => { it('should fire for iframes', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); (await page.goto(server.EMPTY_PAGE))!;
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
}); });
it('should fire for fetches', async () => { it('should fire for fetches', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); (await page.goto(server.EMPTY_PAGE))!;
await page.goto(server.EMPTY_PAGE); await page.evaluate(() => {
await page.evaluate(() => fetch('/empty.html')); return fetch('/empty.html');
});
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
}); });
}); });
@ -74,57 +75,56 @@ describe('network', function () {
it('should work for main frame navigation request', async () => { it('should work for main frame navigation request', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); (await page.goto(server.EMPTY_PAGE))!;
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame()); expect(requests[0]!.frame()).toBe(page.mainFrame());
}); });
itFailsFirefox('should work for subframe navigation request', async () => { itFailsFirefox('should work for subframe navigation request', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); (await page.goto(server.EMPTY_PAGE))!;
const requests = []; const requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.frames()[1]); expect(requests[0]!.frame()).toBe(page.frames()[1]!);
}); });
it('should work for fetch requests', async () => { it('should work for fetch requests', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); (await page.goto(server.EMPTY_PAGE))!;
let requests = []; let requests: HTTPRequest[] = [];
page.on( page.on('request', (request) => {
'request', return !utils.isFavicon(request) && requests.push(request);
(request) => !utils.isFavicon(request) && requests.push(request) });
); await page.evaluate(() => {
await page.evaluate(() => fetch('/digits/1.png')); return fetch('/digits/1.png');
requests = requests.filter( });
(request) => !request.url().includes('favicon') requests = requests.filter((request) => {
); return !request.url().includes('favicon');
});
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame()); expect(requests[0]!.frame()).toBe(page.mainFrame());
}); });
}); });
describe('Request.headers', function () { describe('Request.headers', function () {
itChromeOnly('should define Chrome as user agent header', async () => { itChromeOnly('should define Chrome as user agent header', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.request().headers()['user-agent']).toContain('Chrome'); expect(response.request().headers()['user-agent']).toContain('Chrome');
}); });
itFirefoxOnly('should define Firefox as user agent header', async () => { itFirefoxOnly('should define Firefox as user agent header', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.request().headers()['user-agent']).toContain('Firefox'); expect(response.request().headers()['user-agent']).toContain('Firefox');
}); });
}); });
@ -133,11 +133,11 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.setHeader('foo', 'bar'); res.setHeader('foo', 'bar');
res.end(); res.end();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.headers()['foo']).toBe('bar'); expect(response.headers()['foo']).toBe('bar');
}); });
}); });
@ -147,9 +147,12 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
const initiators = new Map(); const initiators = new Map();
page.on('request', (request) => page.on('request', (request) => {
initiators.set(request.url().split('/').pop(), request.initiator()) return initiators.set(
request.url().split('/').pop(),
request.initiator()
); );
});
await page.goto(server.PREFIX + '/initiator.html'); await page.goto(server.PREFIX + '/initiator.html');
expect(initiators.get('initiator.html').type).toBe('other'); expect(initiators.get('initiator.html').type).toBe('other');
@ -171,11 +174,11 @@ describe('network', function () {
); );
expect(initiators.get('initiator.js').type).toBe('parser'); expect(initiators.get('initiator.js').type).toBe('parser');
expect(initiators.get('injectedfile.js').type).toBe('script'); expect(initiators.get('injectedfile.js').type).toBe('script');
expect(initiators.get('injectedfile.js').stack.callFrames[0].url).toBe( expect(initiators.get('injectedfile.js').stack.callFrames[0]!.url).toBe(
server.PREFIX + '/initiator.js' server.PREFIX + '/initiator.js'
); );
expect(initiators.get('injectedstyle.css').type).toBe('script'); expect(initiators.get('injectedstyle.css').type).toBe('script');
expect(initiators.get('injectedstyle.css').stack.callFrames[0].url).toBe( expect(initiators.get('injectedstyle.css').stack.callFrames[0]!.url).toBe(
server.PREFIX + '/initiator.js' server.PREFIX + '/initiator.js'
); );
expect(initiators.get('initiator.js').url).toBe( expect(initiators.get('initiator.js').url).toBe(
@ -188,7 +191,7 @@ describe('network', function () {
it('should return |false| for non-cached content', async () => { it('should return |false| for non-cached content', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.fromCache()).toBe(false); expect(response.fromCache()).toBe(false);
}); });
@ -196,12 +199,12 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
const responses = new Map(); const responses = new Map();
page.on( page.on('response', (r) => {
'response', return (
(r) =>
!utils.isFavicon(r.request()) && !utils.isFavicon(r.request()) &&
responses.set(r.url().split('/').pop(), r) responses.set(r.url().split('/').pop(), r)
); );
});
// Load and re-load to make sure it's cached. // Load and re-load to make sure it's cached.
await page.goto(server.PREFIX + '/cached/one-style.html'); await page.goto(server.PREFIX + '/cached/one-style.html');
@ -219,7 +222,7 @@ describe('network', function () {
it('should return |false| for non-service-worker content', async () => { it('should return |false| for non-service-worker content', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.fromServiceWorker()).toBe(false); expect(response.fromServiceWorker()).toBe(false);
}); });
@ -227,16 +230,19 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
const responses = new Map(); const responses = new Map();
page.on( page.on('response', (r) => {
'response', return (
(r) => !utils.isFavicon(r) && responses.set(r.url().split('/').pop(), r) !utils.isFavicon(r) && responses.set(r.url().split('/').pop(), r)
); );
});
// Load and re-load to make sure serviceworker is installed and running. // Load and re-load to make sure serviceworker is installed and running.
await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', { await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {
waitUntil: 'networkidle2', waitUntil: 'networkidle2',
}); });
await page.evaluate(async () => await globalThis.activationPromise); await page.evaluate(async () => {
return await (globalThis as any).activationPromise;
});
await page.reload(); await page.reload();
expect(responses.size).toBe(2); expect(responses.size).toBe(2);
@ -251,27 +257,29 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); (await page.goto(server.EMPTY_PAGE))!;
server.setRoute('/post', (req, res) => res.end()); server.setRoute('/post', (_req, res) => {
let request = null; return res.end();
});
let request!: HTTPRequest;
page.on('request', (r) => { page.on('request', (r) => {
if (!utils.isFavicon(r)) { if (!utils.isFavicon(r)) {
request = r; request = r;
} }
}); });
await page.evaluate(() => await page.evaluate(() => {
fetch('./post', { return fetch('./post', {
method: 'POST', method: 'POST',
body: JSON.stringify({ foo: 'bar' }), body: JSON.stringify({ foo: 'bar' }),
}) });
); });
expect(request).toBeTruthy(); expect(request).toBeTruthy();
expect(request.postData()).toBe('{"foo":"bar"}'); expect(request.postData()).toBe('{"foo":"bar"}');
}); });
it('should be |undefined| when there is no post data', async () => { it('should be |undefined| when there is no post data', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.request().postData()).toBe(undefined); expect(response.request().postData()).toBe(undefined);
}); });
}); });
@ -280,7 +288,7 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/simple.json'); const response = (await page.goto(server.PREFIX + '/simple.json'))!;
const responseText = (await response.text()).trimEnd(); const responseText = (await response.text()).trimEnd();
expect(responseText).toBe('{"foo": "bar"}'); expect(responseText).toBe('{"foo": "bar"}');
}); });
@ -288,7 +296,7 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.enableGzip('/simple.json'); server.enableGzip('/simple.json');
const response = await page.goto(server.PREFIX + '/simple.json'); const response = (await page.goto(server.PREFIX + '/simple.json'))!;
expect(response.headers()['content-encoding']).toBe('gzip'); expect(response.headers()['content-encoding']).toBe('gzip');
const responseText = (await response.text()).trimEnd(); const responseText = (await response.text()).trimEnd();
expect(responseText).toBe('{"foo": "bar"}'); expect(responseText).toBe('{"foo": "bar"}');
@ -297,13 +305,15 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRedirect('/foo.html', '/empty.html'); server.setRedirect('/foo.html', '/empty.html');
const response = await page.goto(server.PREFIX + '/foo.html'); const response = (await page.goto(server.PREFIX + '/foo.html'))!;
const redirectChain = response.request().redirectChain(); const redirectChain = response.request().redirectChain();
expect(redirectChain.length).toBe(1); expect(redirectChain.length).toBe(1);
const redirected = redirectChain[0].response(); const redirected = redirectChain[0]!.response()!;
expect(redirected.status()).toBe(302); expect(redirected.status()).toBe(302);
let error = null; let error!: Error;
await redirected.text().catch((error_) => (error = error_)); await redirected.text().catch((error_) => {
return (error = error_);
});
expect(error.message).toContain( expect(error.message).toContain(
'Response body is unavailable for redirect responses' 'Response body is unavailable for redirect responses'
); );
@ -311,10 +321,10 @@ describe('network', function () {
it('should wait until response completes', async () => { it('should wait until response completes', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); (await page.goto(server.EMPTY_PAGE))!;
// Setup server to trap request. // Setup server to trap request.
let serverResponse = null; let serverResponse!: ServerResponse;
server.setRoute('/get', (req, res) => { server.setRoute('/get', (_req, res) => {
serverResponse = res; serverResponse = res;
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header // In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server. // from server.
@ -323,14 +333,17 @@ describe('network', function () {
}); });
// Setup page to trap response. // Setup page to trap response.
let requestFinished = false; let requestFinished = false;
page.on( page.on('requestfinished', (r) => {
'requestfinished', return (requestFinished = requestFinished || r.url().includes('/get'));
(r) => (requestFinished = requestFinished || r.url().includes('/get')) });
);
// send request and wait for server response // send request and wait for server response
const [pageResponse] = await Promise.all([ const [pageResponse] = await Promise.all([
page.waitForResponse((r) => !utils.isFavicon(r.request())), page.waitForResponse((r) => {
page.evaluate(() => fetch('./get', { method: 'GET' })), return !utils.isFavicon(r.request());
}),
page.evaluate(() => {
return fetch('./get', { method: 'GET' });
}),
server.waitForRequest('/get'), server.waitForRequest('/get'),
]); ]);
@ -341,9 +354,15 @@ describe('network', function () {
const responseText = pageResponse.text(); const responseText = pageResponse.text();
// Write part of the response and wait for it to be flushed. // Write part of the response and wait for it to be flushed.
await new Promise((x) => serverResponse.write('wor', x)); await new Promise((x) => {
return serverResponse.write('wor', x);
});
// Finish response. // Finish response.
await new Promise((x) => serverResponse.end('ld!', x)); await new Promise<void>((x) => {
serverResponse.end('ld!', () => {
return x();
});
});
expect(await responseText).toBe('hello world!'); expect(await responseText).toBe('hello world!');
}); });
}); });
@ -352,7 +371,7 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/simple.json'); const response = (await page.goto(server.PREFIX + '/simple.json'))!;
expect(await response.json()).toEqual({ foo: 'bar' }); expect(await response.json()).toEqual({ foo: 'bar' });
}); });
}); });
@ -361,7 +380,7 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const response = await page.goto(server.PREFIX + '/pptr.png'); const response = (await page.goto(server.PREFIX + '/pptr.png'))!;
const imageBuffer = fs.readFileSync( const imageBuffer = fs.readFileSync(
path.join(__dirname, '../assets', 'pptr.png') path.join(__dirname, '../assets', 'pptr.png')
); );
@ -372,7 +391,7 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.enableGzip('/pptr.png'); server.enableGzip('/pptr.png');
const response = await page.goto(server.PREFIX + '/pptr.png'); const response = (await page.goto(server.PREFIX + '/pptr.png'))!;
const imageBuffer = fs.readFileSync( const imageBuffer = fs.readFileSync(
path.join(__dirname, '../assets', 'pptr.png') path.join(__dirname, '../assets', 'pptr.png')
); );
@ -384,7 +403,7 @@ describe('network', function () {
await page.goto(server.PREFIX + '/empty.html'); await page.goto(server.PREFIX + '/empty.html');
server.setRoute('/test.html', (req, res) => { server.setRoute('/test.html', (_req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'x-ping'); res.setHeader('Access-Control-Allow-Headers', 'x-ping');
res.end('Hello World'); res.end('Hello World');
@ -422,22 +441,22 @@ describe('network', function () {
it('should work', async () => { it('should work', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/cool', (req, res) => { server.setRoute('/cool', (_req, res) => {
res.writeHead(200, 'cool!'); res.writeHead(200, 'cool!');
res.end(); res.end();
}); });
const response = await page.goto(server.PREFIX + '/cool'); const response = (await page.goto(server.PREFIX + '/cool'))!;
expect(response.statusText()).toBe('cool!'); expect(response.statusText()).toBe('cool!');
}); });
it('handles missing status text', async () => { it('handles missing status text', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
server.setRoute('/nostatus', (req, res) => { server.setRoute('/nostatus', (_req, res) => {
res.writeHead(200, ''); res.writeHead(200, '');
res.end(); res.end();
}); });
const response = await page.goto(server.PREFIX + '/nostatus'); const response = (await page.goto(server.PREFIX + '/nostatus'))!;
expect(response.statusText()).toBe(''); expect(response.statusText()).toBe('');
}); });
}); });
@ -445,11 +464,13 @@ describe('network', function () {
describeFailsFirefox('Response.timing', function () { describeFailsFirefox('Response.timing', function () {
it('returns timing information', async () => { it('returns timing information', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const responses = []; const responses: HTTPResponse[] = [];
page.on('response', (response) => responses.push(response)); page.on('response', (response) => {
await page.goto(server.EMPTY_PAGE); return responses.push(response);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(responses.length).toBe(1); expect(responses.length).toBe(1);
expect(responses[0].timing().receiveHeadersEnd).toBeGreaterThan(0); expect(responses[0]!.timing()!.receiveHeadersEnd).toBeGreaterThan(0);
}); });
}); });
@ -457,24 +478,26 @@ describe('network', function () {
it('Page.Events.Request', async () => { it('Page.Events.Request', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => requests.push(request)); page.on('request', (request) => {
await page.goto(server.EMPTY_PAGE); return requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
expect(requests[0].resourceType()).toBe('document'); expect(requests[0]!.resourceType()).toBe('document');
expect(requests[0].method()).toBe('GET'); expect(requests[0]!.method()).toBe('GET');
expect(requests[0].response()).toBeTruthy(); expect(requests[0]!.response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true); expect(requests[0]!.frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.frame()!.url()).toBe(server.EMPTY_PAGE);
}); });
it('Page.Events.RequestServedFromCache', async () => { it('Page.Events.RequestServedFromCache', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const cached = []; const cached: string[] = [];
page.on('requestservedfromcache', (r) => page.on('requestservedfromcache', (r) => {
cached.push(r.url().split('/').pop()) return cached.push(r.url().split('/').pop()!);
); });
await page.goto(server.PREFIX + '/cached/one-style.html'); await page.goto(server.PREFIX + '/cached/one-style.html');
expect(cached).toEqual([]); expect(cached).toEqual([]);
@ -485,18 +508,20 @@ describe('network', function () {
it('Page.Events.Response', async () => { it('Page.Events.Response', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const responses = []; const responses: HTTPResponse[] = [];
page.on('response', (response) => responses.push(response)); page.on('response', (response) => {
await page.goto(server.EMPTY_PAGE); return responses.push(response);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(responses.length).toBe(1); expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE); expect(responses[0]!.url()).toBe(server.EMPTY_PAGE);
expect(responses[0].status()).toBe(200); expect(responses[0]!.status()).toBe(200);
expect(responses[0].ok()).toBe(true); expect(responses[0]!.ok()).toBe(true);
expect(responses[0].request()).toBeTruthy(); expect(responses[0]!.request()).toBeTruthy();
const remoteAddress = responses[0].remoteAddress(); const remoteAddress = responses[0]!.remoteAddress();
// Either IPv6 or IPv4, depending on environment. // Either IPv6 or IPv4, depending on environment.
expect( expect(
remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1' remoteAddress.ip!.includes('::1') || remoteAddress.ip === '127.0.0.1'
).toBe(true); ).toBe(true);
expect(remoteAddress.port).toBe(server.PORT); expect(remoteAddress.port).toBe(server.PORT);
}); });
@ -512,61 +537,73 @@ describe('network', function () {
request.continue(); request.continue();
} }
}); });
const failedRequests = []; const failedRequests: HTTPRequest[] = [];
page.on('requestfailed', (request) => failedRequests.push(request)); page.on('requestfailed', (request) => {
return failedRequests.push(request);
});
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
expect(failedRequests.length).toBe(1); expect(failedRequests.length).toBe(1);
expect(failedRequests[0].url()).toContain('one-style.css'); expect(failedRequests[0]!.url()).toContain('one-style.css');
expect(failedRequests[0].response()).toBe(null); expect(failedRequests[0]!.response()).toBe(null);
expect(failedRequests[0].resourceType()).toBe('stylesheet'); expect(failedRequests[0]!.resourceType()).toBe('stylesheet');
if (isChrome) { if (isChrome) {
expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED'); expect(failedRequests[0]!.failure()!.errorText).toBe('net::ERR_FAILED');
} else { } else {
expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE'); expect(failedRequests[0]!.failure()!.errorText).toBe(
'NS_ERROR_FAILURE'
);
} }
expect(failedRequests[0].frame()).toBeTruthy(); expect(failedRequests[0]!.frame()).toBeTruthy();
}); });
it('Page.Events.RequestFinished', async () => { it('Page.Events.RequestFinished', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on('requestfinished', (request) => requests.push(request)); page.on('requestfinished', (request) => {
await page.goto(server.EMPTY_PAGE); return requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
expect(requests[0].response()).toBeTruthy(); expect(requests[0]!.response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true); expect(requests[0]!.frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.frame()!.url()).toBe(server.EMPTY_PAGE);
}); });
it('should fire events in proper order', async () => { it('should fire events in proper order', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const events = []; const events: string[] = [];
page.on('request', () => events.push('request')); page.on('request', () => {
page.on('response', () => events.push('response')); return events.push('request');
page.on('requestfinished', () => events.push('requestfinished')); });
await page.goto(server.EMPTY_PAGE); page.on('response', () => {
return events.push('response');
});
page.on('requestfinished', () => {
return events.push('requestfinished');
});
(await page.goto(server.EMPTY_PAGE))!;
expect(events).toEqual(['request', 'response', 'requestfinished']); expect(events).toEqual(['request', 'response', 'requestfinished']);
}); });
it('should support redirects', async () => { it('should support redirects', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const events = []; const events: string[] = [];
page.on('request', (request) => page.on('request', (request) => {
events.push(`${request.method()} ${request.url()}`) return events.push(`${request.method()} ${request.url()}`);
); });
page.on('response', (response) => page.on('response', (response) => {
events.push(`${response.status()} ${response.url()}`) return events.push(`${response.status()} ${response.url()}`);
); });
page.on('requestfinished', (request) => page.on('requestfinished', (request) => {
events.push(`DONE ${request.url()}`) return events.push(`DONE ${request.url()}`);
); });
page.on('requestfailed', (request) => page.on('requestfailed', (request) => {
events.push(`FAIL ${request.url()}`) return events.push(`FAIL ${request.url()}`);
); });
server.setRedirect('/foo.html', '/empty.html'); server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = server.PREFIX + '/foo.html'; const FOO_URL = server.PREFIX + '/foo.html';
const response = await page.goto(FOO_URL); const response = (await page.goto(FOO_URL))!;
expect(events).toEqual([ expect(events).toEqual([
`GET ${FOO_URL}`, `GET ${FOO_URL}`,
`302 ${FOO_URL}`, `302 ${FOO_URL}`,
@ -579,8 +616,8 @@ describe('network', function () {
// Check redirect chain // Check redirect chain
const redirectChain = response.request().redirectChain(); const redirectChain = response.request().redirectChain();
expect(redirectChain.length).toBe(1); expect(redirectChain.length).toBe(1);
expect(redirectChain[0].url()).toContain('/foo.html'); expect(redirectChain[0]!.url()).toContain('/foo.html');
expect(redirectChain[0].response().remoteAddress().port).toBe( expect(redirectChain[0]!.response()!.remoteAddress().port).toBe(
server.PORT server.PORT
); );
}); });
@ -591,9 +628,9 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = new Map(); const requests = new Map();
page.on('request', (request) => page.on('request', (request) => {
requests.set(request.url().split('/').pop(), request) return requests.set(request.url().split('/').pop(), request);
); });
server.setRedirect('/rrredirect', '/frames/one-frame.html'); server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect'); await page.goto(server.PREFIX + '/rrredirect');
expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
@ -622,10 +659,12 @@ describe('network', function () {
itFailsFirefox('should work when navigating to image', async () => { itFailsFirefox('should work when navigating to image', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => requests.push(request)); page.on('request', (request) => {
await page.goto(server.PREFIX + '/pptr.png'); return requests.push(request);
expect(requests[0].isNavigationRequest()).toBe(true); });
(await page.goto(server.PREFIX + '/pptr.png'))!;
expect(requests[0]!.isNavigationRequest()).toBe(true);
}); });
}); });
@ -645,12 +684,12 @@ describe('network', function () {
it('should throw for non-string header values', async () => { it('should throw for non-string header values', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
try { try {
// @ts-expect-error purposeful bad input // @ts-expect-error purposeful bad input
await page.setExtraHTTPHeaders({ foo: 1 }); await page.setExtraHTTPHeaders({ foo: 1 });
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error.message).toBe( expect(error.message).toBe(
'Expected value of header "foo" to be String, but "number" is found.' 'Expected value of header "foo" to be String, but "number" is found.'
@ -665,11 +704,15 @@ describe('network', function () {
server.setAuth('/empty.html', 'user', 'pass'); server.setAuth('/empty.html', 'user', 'pass');
let response; let response;
try { try {
response = await page.goto(server.EMPTY_PAGE); response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(401); expect(response.status()).toBe(401);
} catch (error) { } catch (error) {
// In headful, an error is thrown instead of 401. // In headful, an error is thrown instead of 401.
if (!error.message.startsWith('net::ERR_INVALID_AUTH_CREDENTIALS')) { if (
!(error as Error).message.startsWith(
'net::ERR_INVALID_AUTH_CREDENTIALS'
)
) {
throw error; throw error;
} }
} }
@ -677,7 +720,7 @@ describe('network', function () {
username: 'user', username: 'user',
password: 'pass', password: 'pass',
}); });
response = await page.reload(); response = (await page.reload())!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it('should fail if wrong credentials', async () => { it('should fail if wrong credentials', async () => {
@ -689,7 +732,7 @@ describe('network', function () {
username: 'foo', username: 'foo',
password: 'bar', password: 'bar',
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(401); expect(response.status()).toBe(401);
}); });
it('should allow disable authentication', async () => { it('should allow disable authentication', async () => {
@ -701,16 +744,25 @@ describe('network', function () {
username: 'user3', username: 'user3',
password: 'pass3', password: 'pass3',
}); });
let response = await page.goto(server.EMPTY_PAGE); let response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
await page.authenticate(null); await page.authenticate({
username: '',
password: '',
});
// Navigate to a different origin to bust Chrome's credential caching. // Navigate to a different origin to bust Chrome's credential caching.
try { try {
response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); response = (await page.goto(
server.CROSS_PROCESS_PREFIX + '/empty.html'
))!;
expect(response.status()).toBe(401); expect(response.status()).toBe(401);
} catch (error) { } catch (error) {
// In headful, an error is thrown instead of 401. // In headful, an error is thrown instead of 401.
if (!error.message.startsWith('net::ERR_INVALID_AUTH_CREDENTIALS')) { if (
!(error as Error).message.startsWith(
'net::ERR_INVALID_AUTH_CREDENTIALS'
)
) {
throw error; throw error;
} }
} }
@ -727,7 +779,9 @@ describe('network', function () {
}); });
const responses = new Map(); const responses = new Map();
page.on('response', (r) => responses.set(r.url().split('/').pop(), r)); page.on('response', (r) => {
return responses.set(r.url().split('/').pop(), r);
});
// Load and re-load to make sure it's cached. // Load and re-load to make sure it's cached.
await page.goto(server.PREFIX + '/cached/one-style.html'); await page.goto(server.PREFIX + '/cached/one-style.html');
@ -745,27 +799,29 @@ describe('network', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
const setCookieString = 'foo=bar'; const setCookieString = 'foo=bar';
server.setRoute('/empty.html', (req, res) => { server.setRoute('/empty.html', (_req, res) => {
res.setHeader('set-cookie', setCookieString); res.setHeader('set-cookie', setCookieString);
res.end('hello world'); res.end('hello world');
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.headers()['set-cookie']).toBe(setCookieString); expect(response.headers()['set-cookie']).toBe(setCookieString);
}); });
it('Same-origin set-cookie subresource', async () => { it('Same-origin set-cookie subresource', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); (await page.goto(server.EMPTY_PAGE))!;
const setCookieString = 'foo=bar'; const setCookieString = 'foo=bar';
server.setRoute('/foo', (req, res) => { server.setRoute('/foo', (_req, res) => {
res.setHeader('set-cookie', setCookieString); res.setHeader('set-cookie', setCookieString);
res.end('hello world'); res.end('hello world');
}); });
const responsePromise = new Promise<HTTPResponse>((resolve) => const responsePromise = new Promise<HTTPResponse>((resolve) => {
page.on('response', (response) => resolve(response)) return page.on('response', (response) => {
); return resolve(response);
});
});
page.evaluate(() => { page.evaluate(() => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', '/foo'); xhr.open('GET', '/foo');
@ -789,7 +845,7 @@ describe('network', function () {
await page.goto(httpsServer.PREFIX + '/empty.html'); await page.goto(httpsServer.PREFIX + '/empty.html');
const setCookieString = 'hello=world'; const setCookieString = 'hello=world';
httpsServer.setRoute('/setcookie.html', (req, res) => { httpsServer.setRoute('/setcookie.html', (_req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('set-cookie', setCookieString); res.setHeader('set-cookie', setCookieString);
res.end(); res.end();

View File

@ -20,7 +20,7 @@ import {
getTestState, getTestState,
describeChromeOnly, describeChromeOnly,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { import {
Browser, Browser,
BrowserContext, BrowserContext,
@ -53,21 +53,19 @@ describeChromeOnly('OOPIF', function () {
afterEach(async () => { afterEach(async () => {
await context.close(); await context.close();
page = null;
context = null;
}); });
after(async () => { after(async () => {
await browser.close(); await browser.close();
browser = null;
}); });
it('should treat OOP iframes and normal iframes the same', async () => { it('should treat OOP iframes and normal iframes the same', async () => {
const { server } = getTestState(); const { server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const framePromise = page.waitForFrame((frame) => const framePromise = page.waitForFrame((frame) => {
frame.url().endsWith('/empty.html') return frame.url().endsWith('/empty.html');
); });
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame( await utils.attachFrame(
page, page,
@ -137,12 +135,16 @@ describeChromeOnly('OOPIF', function () {
const [frame1, frame2] = await Promise.all([frame1Promise, frame2Promise]); const [frame1, frame2] = await Promise.all([frame1Promise, frame2Promise]);
expect(await frame1.evaluate(() => document.location.href)).toMatch( expect(
/one-frame\.html$/ await frame1.evaluate(() => {
); return document.location.href;
expect(await frame2.evaluate(() => document.location.href)).toMatch( })
/frames\/frame\.html$/ ).toMatch(/one-frame\.html$/);
); expect(
await frame2.evaluate(() => {
return document.location.href;
})
).toMatch(/frames\/frame\.html$/);
}); });
it('should support OOP iframes getting detached', async () => { it('should support OOP iframes getting detached', async () => {
const { server } = getTestState(); const { server } = getTestState();
@ -279,9 +281,9 @@ describeChromeOnly('OOPIF', function () {
it('should report oopif frames', async () => { it('should report oopif frames', async () => {
const { server } = getTestState(); const { server } = getTestState();
const frame = page.waitForFrame((frame) => const frame = page.waitForFrame((frame) => {
frame.url().endsWith('/oopif.html') return frame.url().endsWith('/oopif.html');
); });
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
await frame; await frame;
expect(oopifs(context).length).toBe(1); expect(oopifs(context).length).toBe(1);
@ -291,24 +293,32 @@ describeChromeOnly('OOPIF', function () {
it('should wait for inner OOPIFs', async () => { it('should wait for inner OOPIFs', async () => {
const { server } = getTestState(); const { server } = getTestState();
await page.goto(`http://mainframe:${server.PORT}/main-frame.html`); await page.goto(`http://mainframe:${server.PORT}/main-frame.html`);
const frame2 = await page.waitForFrame((frame) => const frame2 = await page.waitForFrame((frame) => {
frame.url().endsWith('inner-frame2.html') return frame.url().endsWith('inner-frame2.html');
); });
expect(oopifs(context).length).toBe(2); expect(oopifs(context).length).toBe(2);
expect(page.frames().filter((frame) => frame.isOOPFrame()).length).toBe(2);
expect( expect(
await frame2.evaluate(() => document.querySelectorAll('button').length) page.frames().filter((frame) => {
return frame.isOOPFrame();
}).length
).toBe(2);
expect(
await frame2.evaluate(() => {
return document.querySelectorAll('button').length;
})
).toStrictEqual(1); ).toStrictEqual(1);
}); });
it('should load oopif iframes with subresources and request interception', async () => { it('should load oopif iframes with subresources and request interception', async () => {
const { server } = getTestState(); const { server } = getTestState();
const frame = page.waitForFrame((frame) => const frame = page.waitForFrame((frame) => {
frame.url().endsWith('/oopif.html') return frame.url().endsWith('/oopif.html');
); });
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
await frame; await frame;
expect(oopifs(context).length).toBe(1); expect(oopifs(context).length).toBe(1);
@ -327,7 +337,7 @@ describeChromeOnly('OOPIF', function () {
server.CROSS_PROCESS_PREFIX + '/empty.html' server.CROSS_PROCESS_PREFIX + '/empty.html'
); );
const frame1 = oopIframe.childFrames()[0]; const frame1 = oopIframe.childFrames()[0]!;
expect(frame1.url()).toMatch(/empty.html$/); expect(frame1.url()).toMatch(/empty.html$/);
await utils.navigateFrame( await utils.navigateFrame(
oopIframe, oopIframe,
@ -367,13 +377,13 @@ describeChromeOnly('OOPIF', function () {
button.innerText = 'click'; button.innerText = 'click';
document.body.appendChild(button); document.body.appendChild(button);
}); });
const button = await frame.waitForSelector('#test-button', { const button = (await frame.waitForSelector('#test-button', {
visible: true, visible: true,
}); }))!;
const result = await button.clickablePoint(); const result = await button.clickablePoint();
expect(result.x).toBeGreaterThan(150); // padding + margin + border left expect(result.x).toBeGreaterThan(150); // padding + margin + border left
expect(result.y).toBeGreaterThan(150); // padding + margin + border top expect(result.y).toBeGreaterThan(150); // padding + margin + border top
const resultBoxModel = await button.boxModel(); const resultBoxModel = (await button.boxModel())!;
for (const quad of [ for (const quad of [
resultBoxModel.content, resultBoxModel.content,
resultBoxModel.border, resultBoxModel.border,
@ -385,7 +395,7 @@ describeChromeOnly('OOPIF', function () {
expect(part.y).toBeGreaterThan(150); // padding + margin + border top expect(part.y).toBeGreaterThan(150); // padding + margin + border top
} }
} }
const resultBoundingBox = await button.boundingBox(); const resultBoundingBox = (await button.boundingBox())!;
expect(resultBoundingBox.x).toBeGreaterThan(150); // padding + margin + border left expect(resultBoundingBox.x).toBeGreaterThan(150); // padding + margin + border left
expect(resultBoundingBox.y).toBeGreaterThan(150); // padding + margin + border top expect(resultBoundingBox.y).toBeGreaterThan(150); // padding + margin + border top
}); });
@ -393,9 +403,9 @@ describeChromeOnly('OOPIF', function () {
it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => { it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
const { server, puppeteer } = getTestState(); const { server, puppeteer } = getTestState();
const frame = page.waitForFrame((frame) => const frame = page.waitForFrame((frame) => {
frame.url().endsWith('/oopif.html') return frame.url().endsWith('/oopif.html');
); });
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
await frame; await frame;
expect(oopifs(context).length).toBe(1); expect(oopifs(context).length).toBe(1);
@ -403,9 +413,9 @@ describeChromeOnly('OOPIF', function () {
const browserURL = 'http://127.0.0.1:21222'; const browserURL = 'http://127.0.0.1:21222';
const browser1 = await puppeteer.connect({ browserURL }); const browser1 = await puppeteer.connect({ browserURL });
const target = await browser1.waitForTarget((target) => const target = await browser1.waitForTarget((target) => {
target.url().endsWith('dynamic-oopif.html') return target.url().endsWith('dynamic-oopif.html');
); });
await target.page(); await target.page();
browser1.disconnect(); browser1.disconnect();
}); });
@ -415,11 +425,11 @@ describeChromeOnly('OOPIF', function () {
await page.goto(server.PREFIX + '/lazy-oopif-frame.html'); await page.goto(server.PREFIX + '/lazy-oopif-frame.html');
await page.setViewport({ width: 1000, height: 1000 }); await page.setViewport({ width: 1000, height: 1000 });
expect(page.frames().map((frame) => frame._hasStartedLoading)).toEqual([ expect(
true, page.frames().map((frame) => {
true, return frame._hasStartedLoading;
false, })
]); ).toEqual([true, true, false]);
}); });
describe('waitForFrame', () => { describe('waitForFrame', () => {
@ -433,13 +443,15 @@ describeChromeOnly('OOPIF', function () {
server.CROSS_PROCESS_PREFIX + '/empty.html' server.CROSS_PROCESS_PREFIX + '/empty.html'
); );
await page.waitForFrame((frame) => frame.url().endsWith('/empty.html')); await page.waitForFrame((frame) => {
return frame.url().endsWith('/empty.html');
});
}); });
}); });
}); });
function oopifs(context: BrowserContext) { function oopifs(context: BrowserContext) {
return context return context.targets().filter((target) => {
.targets() return target._getTargetInfo().type === 'iframe';
.filter((target) => target._getTargetInfo().type === 'iframe'); });
} }

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ import {
getTestState, getTestState,
describeFailsFirefox, describeFailsFirefox,
itFailsWindows, itFailsWindows,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import type { Server, IncomingMessage, ServerResponse } from 'http'; import type { Server, IncomingMessage, ServerResponse } from 'http';
import type { Browser } from '../../lib/cjs/puppeteer/common/Browser.js'; import type { Browser } from '../../lib/cjs/puppeteer/common/Browser.js';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
@ -108,7 +108,7 @@ describeFailsFirefox('request proxy', () => {
}); });
const page = await browser.newPage(); const page = await browser.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -129,7 +129,7 @@ describeFailsFirefox('request proxy', () => {
}); });
const page = await browser.newPage(); const page = await browser.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -148,7 +148,7 @@ describeFailsFirefox('request proxy', () => {
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage(); const page = await context.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -170,7 +170,7 @@ describeFailsFirefox('request proxy', () => {
const context = await browser.createIncognitoBrowserContext(); const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage(); const page = await context.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -195,7 +195,7 @@ describeFailsFirefox('request proxy', () => {
proxyServer: proxyServerUrl, proxyServer: proxyServerUrl,
}); });
const page = await context.newPage(); const page = await context.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -217,7 +217,7 @@ describeFailsFirefox('request proxy', () => {
proxyBypassList: [new URL(emptyPageUrl).host], proxyBypassList: [new URL(emptyPageUrl).host],
}); });
const page = await context.newPage(); const page = await context.newPage();
const response = await page.goto(emptyPageUrl); const response = (await page.goto(emptyPageUrl))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);

View File

@ -18,7 +18,7 @@ import {
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { CustomQueryHandler } from '../../lib/cjs/puppeteer/common/QueryHandler.js'; import { CustomQueryHandler } from '../../lib/cjs/puppeteer/common/QueryHandler.js';
describe('querySelector', function () { describe('querySelector', function () {
@ -29,7 +29,9 @@ describe('querySelector', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<section id="testAttribute">43543</section>'); await page.setContent('<section id="testAttribute">43543</section>');
const idAttribute = await page.$eval('section', (e) => e.id); const idAttribute = await page.$eval('section', (e) => {
return e.id;
});
expect(idAttribute).toBe('testAttribute'); expect(idAttribute).toBe('testAttribute');
}); });
it('should accept arguments', async () => { it('should accept arguments', async () => {
@ -38,7 +40,9 @@ describe('querySelector', function () {
await page.setContent('<section>hello</section>'); await page.setContent('<section>hello</section>');
const text = await page.$eval( const text = await page.$eval(
'section', 'section',
(e, suffix) => e.textContent + suffix, (e, suffix) => {
return e.textContent! + suffix;
},
' world!' ' world!'
); );
expect(text).toBe('hello world!'); expect(text).toBe('hello world!');
@ -47,10 +51,12 @@ describe('querySelector', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<section>hello</section><div> world</div>'); await page.setContent('<section>hello</section><div> world</div>');
const divHandle = await page.$('div'); const divHandle = (await page.$('div'))!;
const text = await page.$eval( const text = await page.$eval(
'section', 'section',
(e, div: HTMLElement) => e.textContent + div.textContent, (e, div) => {
return e.textContent! + (div as HTMLElement).textContent!;
},
divHandle divHandle
); );
expect(text).toBe('hello world'); expect(text).toBe('hello world');
@ -58,10 +64,14 @@ describe('querySelector', function () {
it('should throw error if no element is found', async () => { it('should throw error if no element is found', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
await page await page
.$eval('section', (e) => e.id) .$eval('section', (e) => {
.catch((error_) => (error = error_)); return e.id;
})
.catch((error_) => {
return (error = error_);
});
expect(error.message).toContain( expect(error.message).toContain(
'failed to find element matching selector "section"' 'failed to find element matching selector "section"'
); );
@ -89,39 +99,43 @@ describe('querySelector', function () {
}); });
it('should find first element in shadow', async () => { it('should find first element in shadow', async () => {
const { page } = getTestState(); const { page } = getTestState();
const div = await page.$('pierce/.foo'); const div = (await page.$('pierce/.foo'))!;
const text = await div.evaluate( const text = await div.evaluate((element: Element) => {
(element: Element) => element.textContent return element.textContent;
); });
expect(text).toBe('Hello'); expect(text).toBe('Hello');
}); });
it('should find all elements in shadow', async () => { it('should find all elements in shadow', async () => {
const { page } = getTestState(); const { page } = getTestState();
const divs = await page.$$('pierce/.foo'); const divs = await page.$$('pierce/.foo');
const text = await Promise.all( const text = await Promise.all(
divs.map((div) => divs.map((div) => {
div.evaluate((element: Element) => element.textContent) return div.evaluate((element: Element) => {
) return element.textContent;
});
})
); );
expect(text.join(' ')).toBe('Hello World'); expect(text.join(' ')).toBe('Hello World');
}); });
it('should find first child element', async () => { it('should find first child element', async () => {
const { page } = getTestState(); const { page } = getTestState();
const parentElement = await page.$('html > div'); const parentElement = (await page.$('html > div'))!;
const childElement = await parentElement.$('pierce/div'); const childElement = (await parentElement.$('pierce/div'))!;
const text = await childElement.evaluate( const text = await childElement.evaluate((element: Element) => {
(element: Element) => element.textContent return element.textContent;
); });
expect(text).toBe('Hello'); expect(text).toBe('Hello');
}); });
it('should find all child elements', async () => { it('should find all child elements', async () => {
const { page } = getTestState(); const { page } = getTestState();
const parentElement = await page.$('html > div'); const parentElement = (await page.$('html > div'))!;
const childElements = await parentElement.$$('pierce/div'); const childElements = await parentElement.$$('pierce/div');
const text = await Promise.all( const text = await Promise.all(
childElements.map((div) => childElements.map((div) => {
div.evaluate((element: Element) => element.textContent) return div.evaluate((element: Element) => {
) return element.textContent;
});
})
); );
expect(text.join(' ')).toBe('Hello World'); expect(text.join(' ')).toBe('Hello World');
}); });
@ -137,7 +151,9 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<div>hello</div><div>beautiful</div><div>world!</div>' '<div>hello</div><div>beautiful</div><div>world!</div>'
); );
const divsCount = await page.$$eval('div', (divs) => divs.length); const divsCount = await page.$$eval('div', (divs) => {
return divs.length;
});
expect(divsCount).toBe(3); expect(divsCount).toBe(3);
}); });
it('should accept extra arguments', async () => { it('should accept extra arguments', async () => {
@ -147,7 +163,9 @@ describe('querySelector', function () {
); );
const divsCountPlus5 = await page.$$eval( const divsCountPlus5 = await page.$$eval(
'div', 'div',
(divs, two: number, three: number) => divs.length + two + three, (divs, two, three) => {
return divs.length + (two as number) + (three as number);
},
2, 2,
3 3
); );
@ -158,14 +176,16 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<section>2</section><section>2</section><section>1</section><div>3</div>' '<section>2</section><section>2</section><section>1</section><div>3</div>'
); );
const divHandle = await page.$('div'); const divHandle = (await page.$('div'))!;
const sum = await page.$$eval( const sum = await page.$$eval(
'section', 'section',
(sections, div: HTMLElement) => (sections, div) => {
sections.reduce( return (
(acc, section) => acc + Number(section.textContent), sections.reduce((acc, section) => {
0 return acc + Number(section.textContent);
) + Number(div.textContent), }, 0) + Number((div as HTMLElement).textContent)
);
},
divHandle divHandle
); );
expect(sum).toBe(8); expect(sum).toBe(8);
@ -181,9 +201,11 @@ describe('querySelector', function () {
} }
` `
); );
const sum = await page.$$eval('section', (sections) => const sum = await page.$$eval('section', (sections) => {
sections.reduce((acc, section) => acc + Number(section.textContent), 0) return sections.reduce((acc, section) => {
); return acc + Number(section.textContent);
}, 0);
});
expect(sum).toBe(500500); expect(sum).toBe(500500);
}); });
}); });
@ -193,13 +215,13 @@ describe('querySelector', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const element = await page.$('section'); const element = (await page.$('section'))!;
expect(element).toBeTruthy(); expect(element).toBeTruthy();
}); });
it('should return null for non-existing element', async () => { it('should return null for non-existing element', async () => {
const { page } = getTestState(); const { page } = getTestState();
const element = await page.$('non-existing-element'); const element = (await page.$('non-existing-element'))!;
expect(element).toBe(null); expect(element).toBe(null);
}); });
}); });
@ -211,9 +233,11 @@ describe('querySelector', function () {
await page.setContent('<div>A</div><br/><div>B</div>'); await page.setContent('<div>A</div><br/><div>B</div>');
const elements = await page.$$('div'); const elements = await page.$$('div');
expect(elements.length).toBe(2); expect(elements.length).toBe(2);
const promises = elements.map((element) => const promises = elements.map((element) => {
page.evaluate((e: HTMLElement) => e.textContent, element) return page.evaluate((e: HTMLElement) => {
); return e.textContent;
}, element);
});
expect(await Promise.all(promises)).toEqual(['A', 'B']); expect(await Promise.all(promises)).toEqual(['A', 'B']);
}); });
it('should return empty array if nothing is found', async () => { it('should return empty array if nothing is found', async () => {
@ -231,7 +255,7 @@ describe('querySelector', function () {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const elements = await page.$x('/html/body/section'); const elements = await page.$x('/html/body/section');
expect(elements[0]).toBeTruthy(); expect(elements[0]!).toBeTruthy();
expect(elements.length).toBe(1); expect(elements.length).toBe(1);
}); });
it('should return empty array for non-existing element', async () => { it('should return empty array for non-existing element', async () => {
@ -257,13 +281,12 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="second"><div class="inner">A</div></div></body></html>' '<html><body><div class="second"><div class="inner">A</div></div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const second = await html.$('.second'); const second = (await html.$('.second'))!;
const inner = await second.$('.inner'); const inner = await second.$('.inner');
const content = await page.evaluate( const content = await page.evaluate((e: HTMLElement) => {
(e: HTMLElement) => e.textContent, return e.textContent;
inner }, inner);
);
expect(content).toBe('A'); expect(content).toBe('A');
}); });
@ -273,7 +296,7 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="second"><div class="inner">B</div></div></body></html>' '<html><body><div class="second"><div class="inner">B</div></div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const second = await html.$('.third'); const second = await html.$('.third');
expect(second).toBe(null); expect(second).toBe(null);
}); });
@ -285,11 +308,10 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>' '<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>'
); );
const tweet = await page.$('.tweet'); const tweet = (await page.$('.tweet'))!;
const content = await tweet.$eval( const content = await tweet.$eval('.like', (node) => {
'.like', return (node as HTMLElement).innerText;
(node: HTMLElement) => node.innerText });
);
expect(content).toBe('100'); expect(content).toBe('100');
}); });
@ -299,11 +321,10 @@ describe('querySelector', function () {
const htmlContent = const htmlContent =
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></div>'; '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></div>';
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = (await page.$('#myId'))!;
const content = await elementHandle.$eval( const content = await elementHandle.$eval('.a', (node) => {
'.a', return (node as HTMLElement).innerText;
(node: HTMLElement) => node.innerText });
);
expect(content).toBe('a-child-div'); expect(content).toBe('a-child-div');
}); });
@ -313,10 +334,14 @@ describe('querySelector', function () {
const htmlContent = const htmlContent =
'<div class="a">not-a-child-div</div><div id="myId"></div>'; '<div class="a">not-a-child-div</div><div id="myId"></div>';
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = (await page.$('#myId'))!;
const errorMessage = await elementHandle const errorMessage = await elementHandle
.$eval('.a', (node: HTMLElement) => node.innerText) .$eval('.a', (node) => {
.catch((error) => error.message); return (node as HTMLElement).innerText;
})
.catch((error) => {
return error.message;
});
expect(errorMessage).toBe( expect(errorMessage).toBe(
`Error: failed to find element matching selector ".a"` `Error: failed to find element matching selector ".a"`
); );
@ -329,10 +354,12 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>' '<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>'
); );
const tweet = await page.$('.tweet'); const tweet = (await page.$('.tweet'))!;
const content = await tweet.$$eval('.like', (nodes: HTMLElement[]) => const content = await tweet.$$eval('.like', (nodes) => {
nodes.map((n) => n.innerText) return (nodes as HTMLElement[]).map((n) => {
); return n.innerText;
});
});
expect(content).toEqual(['100', '10']); expect(content).toEqual(['100', '10']);
}); });
@ -342,10 +369,12 @@ describe('querySelector', function () {
const htmlContent = const htmlContent =
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></div>'; '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></div>';
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = (await page.$('#myId'))!;
const content = await elementHandle.$$eval('.a', (nodes: HTMLElement[]) => const content = await elementHandle.$$eval('.a', (nodes) => {
nodes.map((n) => n.innerText) return (nodes as HTMLElement[]).map((n) => {
); return n.innerText;
});
});
expect(content).toEqual(['a1-child-div', 'a2-child-div']); expect(content).toEqual(['a1-child-div', 'a2-child-div']);
}); });
@ -355,11 +384,10 @@ describe('querySelector', function () {
const htmlContent = const htmlContent =
'<div class="a">not-a-child-div</div><div id="myId"></div>'; '<div class="a">not-a-child-div</div><div id="myId"></div>';
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = (await page.$('#myId'))!;
const nodesLength = await elementHandle.$$eval( const nodesLength = await elementHandle.$$eval('.a', (nodes) => {
'.a', return nodes.length;
(nodes) => nodes.length });
);
expect(nodesLength).toBe(0); expect(nodesLength).toBe(0);
}); });
}); });
@ -371,12 +399,14 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div>A</div><br/><div>B</div></body></html>' '<html><body><div>A</div><br/><div>B</div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const elements = await html.$$('div'); const elements = await html.$$('div');
expect(elements.length).toBe(2); expect(elements.length).toBe(2);
const promises = elements.map((element) => const promises = elements.map((element) => {
page.evaluate((e: HTMLElement) => e.textContent, element) return page.evaluate((e: HTMLElement) => {
); return e.textContent;
}, element);
});
expect(await Promise.all(promises)).toEqual(['A', 'B']); expect(await Promise.all(promises)).toEqual(['A', 'B']);
}); });
@ -386,7 +416,7 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><span>A</span><br/><span>B</span></body></html>' '<html><body><span>A</span><br/><span>B</span></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const elements = await html.$$('div'); const elements = await html.$$('div');
expect(elements.length).toBe(0); expect(elements.length).toBe(0);
}); });
@ -400,13 +430,12 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="second"><div class="inner">A</div></div></body></html>' '<html><body><div class="second"><div class="inner">A</div></div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const second = await html.$x(`./body/div[contains(@class, 'second')]`); const second = await html.$x(`./body/div[contains(@class, 'second')]`);
const inner = await second[0].$x(`./div[contains(@class, 'inner')]`); const inner = await second[0]!.$x(`./div[contains(@class, 'inner')]`);
const content = await page.evaluate( const content = await page.evaluate((e: HTMLElement) => {
(e: HTMLElement) => e.textContent, return e.textContent;
inner[0] }, inner[0]!);
);
expect(content).toBe('A'); expect(content).toBe('A');
}); });
@ -416,7 +445,7 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div class="second"><div class="inner">B</div></div></body></html>' '<html><body><div class="second"><div class="inner">B</div></div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const second = await html.$x(`/div[contains(@class, 'third')]`); const second = await html.$x(`/div[contains(@class, 'third')]`);
expect(second).toEqual([]); expect(second).toEqual([]);
}); });
@ -426,8 +455,9 @@ describe('querySelector', function () {
// handler that returns an array instead of a list of nodes. // handler that returns an array instead of a list of nodes.
describe('QueryAll', function () { describe('QueryAll', function () {
const handler: CustomQueryHandler = { const handler: CustomQueryHandler = {
queryAll: (element: Element, selector: string) => queryAll: (element, selector) => {
Array.from(element.querySelectorAll(selector)), return Array.from(element.querySelectorAll(selector));
},
}; };
before(() => { before(() => {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
@ -446,12 +476,14 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><div>A</div><br/><div>B</div></body></html>' '<html><body><div>A</div><br/><div>B</div></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const elements = await html.$$('allArray/div'); const elements = await html.$$('allArray/div');
expect(elements.length).toBe(2); expect(elements.length).toBe(2);
const promises = elements.map((element) => const promises = elements.map((element) => {
page.evaluate((e: HTMLElement) => e.textContent, element) return page.evaluate((e: HTMLElement) => {
); return e.textContent;
}, element);
});
expect(await Promise.all(promises)).toEqual(['A', 'B']); expect(await Promise.all(promises)).toEqual(['A', 'B']);
}); });
@ -461,7 +493,7 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<html><body><span>A</span><br/><span>B</span></body></html>' '<html><body><span>A</span><br/><span>B</span></body></html>'
); );
const html = await page.$('html'); const html = (await page.$('html'))!;
const elements = await html.$$('allArray/div'); const elements = await html.$$('allArray/div');
expect(elements.length).toBe(0); expect(elements.length).toBe(0);
}); });
@ -471,10 +503,9 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<div>hello</div><div>beautiful</div><div>world!</div>' '<div>hello</div><div>beautiful</div><div>world!</div>'
); );
const divsCount = await page.$$eval( const divsCount = await page.$$eval('allArray/div', (divs) => {
'allArray/div', return divs.length;
(divs) => divs.length });
);
expect(divsCount).toBe(3); expect(divsCount).toBe(3);
}); });
it('$$eval should accept extra arguments', async () => { it('$$eval should accept extra arguments', async () => {
@ -484,7 +515,9 @@ describe('querySelector', function () {
); );
const divsCountPlus5 = await page.$$eval( const divsCountPlus5 = await page.$$eval(
'allArray/div', 'allArray/div',
(divs, two: number, three: number) => divs.length + two + three, (divs, two, three) => {
return divs.length + (two as number) + (three as number);
},
2, 2,
3 3
); );
@ -495,14 +528,16 @@ describe('querySelector', function () {
await page.setContent( await page.setContent(
'<section>2</section><section>2</section><section>1</section><div>3</div>' '<section>2</section><section>2</section><section>1</section><div>3</div>'
); );
const divHandle = await page.$('div'); const divHandle = (await page.$('div'))!;
const sum = await page.$$eval( const sum = await page.$$eval(
'allArray/section', 'allArray/section',
(sections, div: HTMLElement) => (sections, div) => {
sections.reduce( return (
(acc, section) => acc + Number(section.textContent), sections.reduce((acc, section) => {
0 return acc + Number(section.textContent);
) + Number(div.textContent), }, 0) + Number((div as HTMLElement).textContent)
);
},
divHandle divHandle
); );
expect(sum).toBe(8); expect(sum).toBe(8);
@ -518,9 +553,11 @@ describe('querySelector', function () {
} }
` `
); );
const sum = await page.$$eval('allArray/section', (sections) => const sum = await page.$$eval('allArray/section', (sections) => {
sections.reduce((acc, section) => acc + Number(section.textContent), 0) return sections.reduce((acc, section) => {
); return acc + Number(section.textContent);
}, 0);
});
expect(sum).toBe(500500); expect(sum).toBe(500500);
}); });
}); });

View File

@ -23,9 +23,13 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { ActionResult } from '../../lib/cjs/puppeteer/api-docs-entry.js'; import { ConsoleMessage } from '../../lib/cjs/puppeteer/common/ConsoleMessage.js';
import { InterceptResolutionAction } from '../../lib/cjs/puppeteer/common/HTTPRequest.js'; import {
ActionResult,
HTTPRequest,
InterceptResolutionAction,
} from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
describe('request interception', function () { describe('request interception', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -67,8 +71,8 @@ describe('request interception', function () {
} }
}); });
page.on('response', (response) => { page.on('response', (response) => {
const { xaction } = response.headers(); const { xaction } = response!.headers();
if (response.url().endsWith('.css') && !!xaction) { if (response!.url().endsWith('.css') && !!xaction) {
actionResults.push(xaction as ActionResult); actionResults.push(xaction as ActionResult);
} }
}); });
@ -78,22 +82,24 @@ describe('request interception', function () {
} }
}); });
const response = await (async () => { const response = (await (async () => {
if (expectedAction === 'continue') { if (expectedAction === 'continue') {
const [serverRequest, response] = await Promise.all([ const [serverRequest, response] = await Promise.all([
server.waitForRequest('/one-style.css'), server.waitForRequest('/one-style.css'),
page.goto(server.PREFIX + '/one-style.html'), page.goto(server.PREFIX + '/one-style.html'),
]); ]);
actionResults.push(serverRequest.headers.xaction as ActionResult); actionResults.push(
serverRequest.headers['xaction'] as ActionResult
);
return response; return response;
} else { } else {
return await page.goto(server.PREFIX + '/one-style.html'); return await page.goto(server.PREFIX + '/one-style.html');
} }
})(); })())!;
expect(actionResults.length).toBe(1); expect(actionResults.length).toBe(1);
expect(actionResults[0]).toBe(expectedAction); expect(actionResults[0]!).toBe(expectedAction);
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
}); });
} }
@ -113,12 +119,12 @@ describe('request interception', function () {
expect(request.isNavigationRequest()).toBe(true); expect(request.isNavigationRequest()).toBe(true);
expect(request.resourceType()).toBe('document'); expect(request.resourceType()).toBe('document');
expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe('about:blank'); expect(request.frame()!.url()).toBe('about:blank');
request.continue({}, 0); request.continue({}, 0);
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
expect(response.remoteAddress().port).toBe(server.PORT); expect(response!.remoteAddress().port).toBe(server.PORT);
}); });
// @see https://github.com/puppeteer/puppeteer/pull/3105 // @see https://github.com/puppeteer/puppeteer/pull/3105
it('should work when POST is redirected with 302', async () => { it('should work when POST is redirected with 302', async () => {
@ -127,14 +133,18 @@ describe('request interception', function () {
server.setRedirect('/rredirect', '/empty.html'); server.setRedirect('/rredirect', '/empty.html');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
await page.setContent(` await page.setContent(`
<form action='/rredirect' method='post'> <form action='/rredirect' method='post'>
<input type="hidden" id="foo" name="foo" value="FOOBAR"> <input type="hidden" id="foo" name="foo" value="FOOBAR">
</form> </form>
`); `);
await Promise.all([ await Promise.all([
page.$eval('form', (form: HTMLFormElement) => form.submit()), page.$eval('form', (form) => {
return (form as HTMLFormElement).submit();
}),
page.waitForNavigation(), page.waitForNavigation(),
]); ]);
}); });
@ -179,7 +189,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
if (!utils.isFavicon(request)) { if (!utils.isFavicon(request)) {
requests.push(request); requests.push(request);
@ -187,8 +197,8 @@ describe('request interception', function () {
request.continue({}, 0); request.continue({}, 0);
}); });
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
expect(requests[1].url()).toContain('/one-style.css'); expect(requests[1]!.url()).toContain('/one-style.css');
expect(requests[1].headers().referer).toContain('/one-style.html'); expect(requests[1]!.headers()['referer']).toContain('/one-style.html');
}); });
it('should properly return navigation response when URL has cookies', async () => { it('should properly return navigation response when URL has cookies', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -199,15 +209,19 @@ describe('request interception', function () {
// Setup request interception. // Setup request interception.
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const response = await page.reload(); const response = await page.reload();
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
}); });
it('should stop intercepting', async () => { it('should stop intercepting', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.once('request', (request) => request.continue({}, 0)); page.once('request', (request) => {
return request.continue({}, 0);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(false); await page.setRequestInterception(false);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -224,7 +238,7 @@ describe('request interception', function () {
request.continue({}, 0); request.continue({}, 0);
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
}); });
// @see https://github.com/puppeteer/puppeteer/issues/4337 // @see https://github.com/puppeteer/puppeteer/issues/4337
it('should work with redirect inside sync XHR', async () => { it('should work with redirect inside sync XHR', async () => {
@ -233,7 +247,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
server.setRedirect('/logo.png', '/pptr.png'); server.setRedirect('/logo.png', '/pptr.png');
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const status = await page.evaluate(async () => { const status = await page.evaluate(async () => {
const request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open('GET', '/logo.png', false); // `false` makes the request synchronous request.open('GET', '/logo.png', false); // `false` makes the request synchronous
@ -252,7 +268,7 @@ describe('request interception', function () {
request.continue({}, 0); request.continue({}, 0);
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
}); });
it('should be abortable', async () => { it('should be abortable', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -266,10 +282,12 @@ describe('request interception', function () {
} }
}); });
let failedRequests = 0; let failedRequests = 0;
page.on('requestfailed', () => ++failedRequests); page.on('requestfailed', () => {
return ++failedRequests;
});
const response = await page.goto(server.PREFIX + '/one-style.html'); const response = await page.goto(server.PREFIX + '/one-style.html');
expect(response.ok()).toBe(true); expect(response!.ok()).toBe(true);
expect(response.request().failure()).toBe(null); expect(response!.request().failure()).toBe(null);
expect(failedRequests).toBe(1); expect(failedRequests).toBe(1);
}); });
it('should be able to access the error reason', async () => { it('should be able to access the error reason', async () => {
@ -294,11 +312,13 @@ describe('request interception', function () {
page.on('request', (request) => { page.on('request', (request) => {
request.abort('internetdisconnected', 0); request.abort('internetdisconnected', 0);
}); });
let failedRequest = null; let failedRequest!: HTTPRequest;
page.on('requestfailed', (request) => (failedRequest = request)); page.on('requestfailed', (request) => {
return (failedRequest = request);
});
await page.goto(server.EMPTY_PAGE).catch(() => {}); await page.goto(server.EMPTY_PAGE).catch(() => {});
expect(failedRequest).toBeTruthy(); expect(failedRequest).toBeTruthy();
expect(failedRequest.failure().errorText).toBe( expect(failedRequest.failure()!.errorText).toBe(
'net::ERR_INTERNET_DISCONNECTED' 'net::ERR_INTERNET_DISCONNECTED'
); );
}); });
@ -309,7 +329,9 @@ describe('request interception', function () {
referer: 'http://google.com/', referer: 'http://google.com/',
}); });
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/grid.html'), server.waitForRequest('/grid.html'),
page.goto(server.PREFIX + '/grid.html'), page.goto(server.PREFIX + '/grid.html'),
@ -320,9 +342,13 @@ describe('request interception', function () {
const { page, server, isChrome } = getTestState(); const { page, server, isChrome } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.abort('failed', 0)); page.on('request', (request) => {
let error = null; return request.abort('failed', 0);
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); });
let error!: Error;
await page.goto(server.EMPTY_PAGE).catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
if (isChrome) { if (isChrome) {
expect(error.message).toContain('net::ERR_FAILED'); expect(error.message).toContain('net::ERR_FAILED');
@ -334,7 +360,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue({}, 0); request.continue({}, 0);
requests.push(request); requests.push(request);
@ -355,17 +381,17 @@ describe('request interception', function () {
const response = await page.goto( const response = await page.goto(
server.PREFIX + '/non-existing-page.html' server.PREFIX + '/non-existing-page.html'
); );
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
expect(response.url()).toContain('empty.html'); expect(response!.url()).toContain('empty.html');
expect(requests.length).toBe(5); expect(requests.length).toBe(5);
expect(requests[2].resourceType()).toBe('document'); expect(requests[2]!.resourceType()).toBe('document');
// Check redirect chain // Check redirect chain
const redirectChain = response.request().redirectChain(); const redirectChain = response!.request().redirectChain();
expect(redirectChain.length).toBe(4); expect(redirectChain.length).toBe(4);
expect(redirectChain[0].url()).toContain('/non-existing-page.html'); expect(redirectChain[0]!.url()).toContain('/non-existing-page.html');
expect(redirectChain[2].url()).toContain('/non-existing-page-3.html'); expect(redirectChain[2]!.url()).toContain('/non-existing-page-3.html');
for (let i = 0; i < redirectChain.length; ++i) { for (let i = 0; i < redirectChain.length; ++i) {
const request = redirectChain[i]; const request = redirectChain[i]!;
expect(request.isNavigationRequest()).toBe(true); expect(request.isNavigationRequest()).toBe(true);
expect(request.redirectChain().indexOf(request)).toBe(i); expect(request.redirectChain().indexOf(request)).toBe(i);
} }
@ -374,7 +400,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue({}, 0); request.continue({}, 0);
if (!utils.isFavicon(request)) { if (!utils.isFavicon(request)) {
@ -384,21 +410,21 @@ describe('request interception', function () {
server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/two-style.css', '/three-style.css');
server.setRedirect('/three-style.css', '/four-style.css'); server.setRedirect('/three-style.css', '/four-style.css');
server.setRoute('/four-style.css', (req, res) => server.setRoute('/four-style.css', (_req, res) => {
res.end('body {box-sizing: border-box; }') return res.end('body {box-sizing: border-box; }');
); });
const response = await page.goto(server.PREFIX + '/one-style.html'); const response = await page.goto(server.PREFIX + '/one-style.html');
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
expect(response.url()).toContain('one-style.html'); expect(response!.url()).toContain('one-style.html');
expect(requests.length).toBe(5); expect(requests.length).toBe(5);
expect(requests[0].resourceType()).toBe('document'); expect(requests[0]!.resourceType()).toBe('document');
expect(requests[1].resourceType()).toBe('stylesheet'); expect(requests[1]!.resourceType()).toBe('stylesheet');
// Check redirect chain // Check redirect chain
const redirectChain = requests[1].redirectChain(); const redirectChain = requests[1]!.redirectChain();
expect(redirectChain.length).toBe(3); expect(redirectChain.length).toBe(3);
expect(redirectChain[0].url()).toContain('/one-style.css'); expect(redirectChain[0]!.url()).toContain('/one-style.css');
expect(redirectChain[2].url()).toContain('/three-style.css'); expect(redirectChain[2]!.url()).toContain('/three-style.css');
}); });
it('should be able to abort redirects', async () => { it('should be able to abort redirects', async () => {
const { page, server, isChrome } = getTestState(); const { page, server, isChrome } = getTestState();
@ -416,9 +442,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const result = await page.evaluate(async () => { const result = await page.evaluate(async () => {
try { try {
await fetch('/non-existing.json'); return await fetch('/non-existing.json');
} catch (error) { } catch (error) {
return error.message; return (error as Error).message;
} }
}); });
if (isChrome) { if (isChrome) {
@ -432,7 +458,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let responseCount = 1; let responseCount = 1;
server.setRoute('/zzz', (req, res) => res.end(responseCount++ * 11 + '')); server.setRoute('/zzz', (_req, res) => {
return res.end(responseCount++ * 11 + '');
});
await page.setRequestInterception(true); await page.setRequestInterception(true);
let spinner = false; let spinner = false;
@ -445,69 +473,82 @@ describe('request interception', function () {
spinner ? request.abort('failed', 0) : request.continue({}, 0); spinner ? request.abort('failed', 0) : request.continue({}, 0);
spinner = !spinner; spinner = !spinner;
}); });
const results = await page.evaluate(() => const results = await page.evaluate(() => {
Promise.all([ return Promise.all([
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response!.text();
})
.catch(() => {
return 'FAILED';
}),
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response!.text();
})
.catch(() => {
return 'FAILED';
}),
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response!.text();
]) })
); .catch(() => {
return 'FAILED';
}),
]);
});
expect(results).toEqual(['11', 'FAILED', '22']); expect(results).toEqual(['11', 'FAILED', '22']);
}); });
it('should navigate to dataURL and fire dataURL requests', async () => { it('should navigate to dataURL and fire dataURL requests', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
requests.push(request); requests.push(request);
request.continue({}, 0); request.continue({}, 0);
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const response = await page.goto(dataURL); const response = await page.goto(dataURL);
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL); expect(requests[0]!.url()).toBe(dataURL);
}); });
it('should be able to fetch dataURL and fire dataURL requests', async () => { it('should be able to fetch dataURL and fire dataURL requests', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
!utils.isFavicon(request) && requests.push(request); !utils.isFavicon(request) && requests.push(request);
request.continue({}, 0); request.continue({}, 0);
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const text = await page.evaluate( const text = await page.evaluate((url: string) => {
(url: string) => fetch(url).then((r) => r.text()), return fetch(url).then((r) => {
dataURL return r.text();
); });
}, dataURL);
expect(text).toBe('<div>yo</div>'); expect(text).toBe('<div>yo</div>');
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL); expect(requests[0]!.url()).toBe(dataURL);
}); });
it('should navigate to URL with hash and fire requests without hash', async () => { it('should navigate to URL with hash and fire requests without hash', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
requests.push(request); requests.push(request);
request.continue({}, 0); request.continue({}, 0);
}); });
const response = await page.goto(server.EMPTY_PAGE + '#hash'); const response = await page.goto(server.EMPTY_PAGE + '#hash');
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response!.url()).toBe(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
}); });
it('should work with encoded server', async () => { it('should work with encoded server', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -515,20 +556,26 @@ describe('request interception', function () {
// The requestWillBeSent will report encoded URL, whereas interception will // The requestWillBeSent will report encoded URL, whereas interception will
// report URL as-is. @see crbug.com/759388 // report URL as-is. @see crbug.com/759388
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const response = await page.goto( const response = await page.goto(
server.PREFIX + '/some nonexisting page' server.PREFIX + '/some nonexisting page'
); );
expect(response.status()).toBe(404); expect(response!.status()).toBe(404);
}); });
it('should work with badly encoded server', async () => { it('should work with badly encoded server', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); server.setRoute('/malformed?rnd=%911', (_req, res) => {
page.on('request', (request) => request.continue({}, 0)); return res.end();
});
page.on('request', (request) => {
return request.continue({}, 0);
});
const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); const response = await page.goto(server.PREFIX + '/malformed?rnd=%911');
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
}); });
it('should work with encoded server - 2', async () => { it('should work with encoded server - 2', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -536,7 +583,7 @@ describe('request interception', function () {
// The requestWillBeSent will report URL as-is, whereas interception will // The requestWillBeSent will report URL as-is, whereas interception will
// report encoded URL for stylesheet. @see crbug.com/759388 // report encoded URL for stylesheet. @see crbug.com/759388
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue({}, 0); request.continue({}, 0);
requests.push(request); requests.push(request);
@ -544,39 +591,47 @@ describe('request interception', function () {
const response = await page.goto( const response = await page.goto(
`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>` `data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`
); );
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
expect(requests[1].response().status()).toBe(404); expect(requests[1]!.response()!.status()).toBe(404);
}); });
it('should not throw "Invalid Interception Id" if the request was cancelled', async () => { it('should not throw "Invalid Interception Id" if the request was cancelled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setContent('<iframe></iframe>'); await page.setContent('<iframe></iframe>');
await page.setRequestInterception(true); await page.setRequestInterception(true);
let request = null; let request!: HTTPRequest;
page.on('request', async (r) => (request = r)); page.on('request', async (r) => {
return (request = r);
});
page.$eval( page.$eval(
'iframe', 'iframe',
(frame: HTMLIFrameElement, url: string) => (frame.src = url), (frame, url) => {
return ((frame as HTMLIFrameElement).src = url as string);
},
server.EMPTY_PAGE server.EMPTY_PAGE
), ),
// Wait for request interception. // Wait for request interception.
await utils.waitEvent(page, 'request'); await utils.waitEvent(page, 'request');
// Delete frame to cause request to be canceled. // Delete frame to cause request to be canceled.
await page.$eval('iframe', (frame) => frame.remove()); await page.$eval('iframe', (frame) => {
let error = null; return frame.remove();
await request.continue({}, 0).catch((error_) => (error = error_)); });
expect(error).toBe(null); let error!: Error;
await request.continue({}, 0).catch((error_) => {
return (error = error_);
});
expect(error).toBeUndefined();
}); });
it('should throw if interception is not enabled', async () => { it('should throw if interception is not enabled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error = null; let error!: Error;
page.on('request', async (request) => { page.on('request', async (request) => {
try { try {
await request.continue({}, 0); await request.continue({}, 0);
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -606,10 +661,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(false); await page.setCacheEnabled(false);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const cached = []; const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r)); page.on('requestservedfromcache', (r) => {
return cached.push(r);
});
await page.reload(); await page.reload();
expect(cached.length).toBe(0); expect(cached.length).toBe(0);
@ -622,10 +681,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(true); await page.setCacheEnabled(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
const cached = []; const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r)); page.on('requestservedfromcache', (r) => {
return cached.push(r);
});
await page.reload(); await page.reload();
expect(cached.length).toBe(1); expect(cached.length).toBe(1);
@ -635,10 +698,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(true); await page.setCacheEnabled(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
await page.goto(server.PREFIX + '/cached/one-style-font.html'); await page.goto(server.PREFIX + '/cached/one-style-font.html');
await page.waitForResponse((r) => r.url().endsWith('/one-style.woff')); await page.waitForResponse((r) => {
return r.url().endsWith('/one-style.woff');
});
}); });
}); });
@ -647,7 +714,9 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue({}, 0)); page.on('request', (request) => {
return request.continue({}, 0);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
}); });
it('should amend HTTP headers', async () => { it('should amend HTTP headers', async () => {
@ -662,7 +731,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')), page.evaluate(() => {
return fetch('/sleep.zzz');
}),
]); ]);
expect(request.headers['foo']).toBe('bar'); expect(request.headers['foo']).toBe('bar');
}); });
@ -676,8 +747,10 @@ describe('request interception', function () {
: undefined; : undefined;
request.continue({ url: redirectURL }, 0); request.continue({ url: redirectURL }, 0);
}); });
let consoleMessage = null; let consoleMessage!: ConsoleMessage;
page.on('console', (msg) => (consoleMessage = msg)); page.on('console', (msg) => {
return (consoleMessage = msg);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(page.url()).toBe(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE);
expect(consoleMessage.text()).toBe('yellow'); expect(consoleMessage.text()).toBe('yellow');
@ -693,7 +766,9 @@ describe('request interception', function () {
}); });
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')), page.evaluate(() => {
return fetch('/sleep.zzz');
}),
]); ]);
expect(request.method).toBe('POST'); expect(request.method).toBe('POST');
}); });
@ -708,9 +783,9 @@ describe('request interception', function () {
}); });
const [serverRequest] = await Promise.all([ const [serverRequest] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => page.evaluate(() => {
fetch('/sleep.zzz', { method: 'POST', body: 'birdy' }) return fetch('/sleep.zzz', { method: 'POST', body: 'birdy' });
), }),
]); ]);
expect(await serverRequest.postBody).toBe('doggo'); expect(await serverRequest.postBody).toBe('doggo');
}); });
@ -748,11 +823,13 @@ describe('request interception', function () {
); );
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.status()).toBe(201); expect(response!.status()).toBe(201);
expect(response.headers().foo).toBe('bar'); expect(response!.headers()['foo']).toBe('bar');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should be able to access the response', async () => { it('should be able to access the response', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -789,11 +866,13 @@ describe('request interception', function () {
); );
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.status()).toBe(422); expect(response!.status()).toBe(422);
expect(response.statusText()).toBe('Unprocessable Entity'); expect(response!.statusText()).toBe('Unprocessable Entity');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should redirect', async () => { it('should redirect', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -815,11 +894,11 @@ describe('request interception', function () {
); );
}); });
const response = await page.goto(server.PREFIX + '/rrredirect'); const response = await page.goto(server.PREFIX + '/rrredirect');
expect(response.request().redirectChain().length).toBe(1); expect(response!.request().redirectChain().length).toBe(1);
expect(response.request().redirectChain()[0].url()).toBe( expect(response!.request().redirectChain()[0]!.url()).toBe(
server.PREFIX + '/rrredirect' server.PREFIX + '/rrredirect'
); );
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response!.url()).toBe(server.EMPTY_PAGE);
}); });
it('should allow mocking binary responses', async () => { it('should allow mocking binary responses', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -841,9 +920,11 @@ describe('request interception', function () {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = PREFIX + '/does-not-exist.png'; img.src = PREFIX + '/does-not-exist.png';
document.body.appendChild(img); document.body.appendChild(img);
return new Promise((fulfill) => (img.onload = fulfill)); return new Promise((fulfill) => {
return (img.onload = fulfill);
});
}, server.PREFIX); }, server.PREFIX);
const img = await page.$('img'); const img = (await page.$('img'))!;
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
}); });
it('should stringify intercepted request response headers', async () => { it('should stringify intercepted request response headers', async () => {
@ -863,12 +944,14 @@ describe('request interception', function () {
); );
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.status()).toBe(200); expect(response!.status()).toBe(200);
const headers = response.headers(); const headers = response!.headers();
expect(headers.foo).toBe('true'); expect(headers['foo']).toBe('true');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should indicate already-handled if an intercept has been handled', async () => { it('should indicate already-handled if an intercept has been handled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();

View File

@ -23,7 +23,9 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
import { HTTPRequest } from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
import { ConsoleMessage } from '../../lib/cjs/puppeteer/common/ConsoleMessage.js';
describe('request interception', function () { describe('request interception', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -46,10 +48,10 @@ describe('request interception', function () {
expect(request.isNavigationRequest()).toBe(true); expect(request.isNavigationRequest()).toBe(true);
expect(request.resourceType()).toBe('document'); expect(request.resourceType()).toBe('document');
expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe('about:blank'); expect(request.frame()!.url()).toBe('about:blank');
request.continue(); request.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.remoteAddress().port).toBe(server.PORT); expect(response.remoteAddress().port).toBe(server.PORT);
}); });
@ -60,14 +62,18 @@ describe('request interception', function () {
server.setRedirect('/rredirect', '/empty.html'); server.setRedirect('/rredirect', '/empty.html');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
await page.setContent(` await page.setContent(`
<form action='/rredirect' method='post'> <form action='/rredirect' method='post'>
<input type="hidden" id="foo" name="foo" value="FOOBAR"> <input type="hidden" id="foo" name="foo" value="FOOBAR">
</form> </form>
`); `);
await Promise.all([ await Promise.all([
page.$eval('form', (form: HTMLFormElement) => form.submit()), page.$eval('form', (form) => {
return (form as HTMLFormElement).submit();
}),
page.waitForNavigation(), page.waitForNavigation(),
]); ]);
}); });
@ -109,7 +115,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
if (!utils.isFavicon(request)) { if (!utils.isFavicon(request)) {
requests.push(request); requests.push(request);
@ -117,8 +123,8 @@ describe('request interception', function () {
request.continue(); request.continue();
}); });
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
expect(requests[1].url()).toContain('/one-style.css'); expect(requests[1]!.url()).toContain('/one-style.css');
expect(requests[1].headers().referer).toContain('/one-style.html'); expect(requests[1]!.headers()['referer']).toContain('/one-style.html');
}); });
it('should properly return navigation response when URL has cookies', async () => { it('should properly return navigation response when URL has cookies', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -129,15 +135,19 @@ describe('request interception', function () {
// Setup request interception. // Setup request interception.
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
const response = await page.reload(); return request.continue();
});
const response = (await page.reload())!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it('should stop intercepting', async () => { it('should stop intercepting', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.once('request', (request) => request.continue()); page.once('request', (request) => {
return request.continue();
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(false); await page.setRequestInterception(false);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -153,7 +163,7 @@ describe('request interception', function () {
expect(request.headers()['foo']).toBe('bar'); expect(request.headers()['foo']).toBe('bar');
request.continue(); request.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
// @see https://github.com/puppeteer/puppeteer/issues/4337 // @see https://github.com/puppeteer/puppeteer/issues/4337
@ -163,7 +173,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
server.setRedirect('/logo.png', '/pptr.png'); server.setRedirect('/logo.png', '/pptr.png');
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
const status = await page.evaluate(async () => { const status = await page.evaluate(async () => {
const request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open('GET', '/logo.png', false); // `false` makes the request synchronous request.open('GET', '/logo.png', false); // `false` makes the request synchronous
@ -181,7 +193,7 @@ describe('request interception', function () {
expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); expect(request.headers()['referer']).toBe(server.EMPTY_PAGE);
request.continue(); request.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
it('should be abortable', async () => { it('should be abortable', async () => {
@ -196,8 +208,10 @@ describe('request interception', function () {
} }
}); });
let failedRequests = 0; let failedRequests = 0;
page.on('requestfailed', () => ++failedRequests); page.on('requestfailed', () => {
const response = await page.goto(server.PREFIX + '/one-style.html'); return ++failedRequests;
});
const response = (await page.goto(server.PREFIX + '/one-style.html'))!;
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.request().failure()).toBe(null); expect(response.request().failure()).toBe(null);
expect(failedRequests).toBe(1); expect(failedRequests).toBe(1);
@ -209,11 +223,13 @@ describe('request interception', function () {
page.on('request', (request) => { page.on('request', (request) => {
request.abort('internetdisconnected'); request.abort('internetdisconnected');
}); });
let failedRequest = null; let failedRequest!: HTTPRequest;
page.on('requestfailed', (request) => (failedRequest = request)); page.on('requestfailed', (request) => {
return (failedRequest = request);
});
await page.goto(server.EMPTY_PAGE).catch(() => {}); await page.goto(server.EMPTY_PAGE).catch(() => {});
expect(failedRequest).toBeTruthy(); expect(failedRequest).toBeTruthy();
expect(failedRequest.failure().errorText).toBe( expect(failedRequest.failure()!.errorText).toBe(
'net::ERR_INTERNET_DISCONNECTED' 'net::ERR_INTERNET_DISCONNECTED'
); );
}); });
@ -224,7 +240,9 @@ describe('request interception', function () {
referer: 'http://google.com/', referer: 'http://google.com/',
}); });
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/grid.html'), server.waitForRequest('/grid.html'),
page.goto(server.PREFIX + '/grid.html'), page.goto(server.PREFIX + '/grid.html'),
@ -235,9 +253,13 @@ describe('request interception', function () {
const { page, server, isChrome } = getTestState(); const { page, server, isChrome } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.abort()); page.on('request', (request) => {
let error = null; return request.abort();
await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); });
let error!: Error;
await page.goto(server.EMPTY_PAGE).catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
if (isChrome) { if (isChrome) {
expect(error.message).toContain('net::ERR_FAILED'); expect(error.message).toContain('net::ERR_FAILED');
@ -249,7 +271,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue(); request.continue();
requests.push(request); requests.push(request);
@ -267,20 +289,20 @@ describe('request interception', function () {
'/non-existing-page-4.html' '/non-existing-page-4.html'
); );
server.setRedirect('/non-existing-page-4.html', '/empty.html'); server.setRedirect('/non-existing-page-4.html', '/empty.html');
const response = await page.goto( const response = (await page.goto(
server.PREFIX + '/non-existing-page.html' server.PREFIX + '/non-existing-page.html'
); ))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toContain('empty.html'); expect(response.url()).toContain('empty.html');
expect(requests.length).toBe(5); expect(requests.length).toBe(5);
expect(requests[2].resourceType()).toBe('document'); expect(requests[2]!.resourceType()).toBe('document');
// Check redirect chain // Check redirect chain
const redirectChain = response.request().redirectChain(); const redirectChain = response.request().redirectChain();
expect(redirectChain.length).toBe(4); expect(redirectChain.length).toBe(4);
expect(redirectChain[0].url()).toContain('/non-existing-page.html'); expect(redirectChain[0]!.url()).toContain('/non-existing-page.html');
expect(redirectChain[2].url()).toContain('/non-existing-page-3.html'); expect(redirectChain[2]!.url()).toContain('/non-existing-page-3.html');
for (let i = 0; i < redirectChain.length; ++i) { for (let i = 0; i < redirectChain.length; ++i) {
const request = redirectChain[i]; const request = redirectChain[i]!;
expect(request.isNavigationRequest()).toBe(true); expect(request.isNavigationRequest()).toBe(true);
expect(request.redirectChain().indexOf(request)).toBe(i); expect(request.redirectChain().indexOf(request)).toBe(i);
} }
@ -289,7 +311,7 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue(); request.continue();
if (!utils.isFavicon(request)) { if (!utils.isFavicon(request)) {
@ -299,21 +321,21 @@ describe('request interception', function () {
server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/two-style.css', '/three-style.css');
server.setRedirect('/three-style.css', '/four-style.css'); server.setRedirect('/three-style.css', '/four-style.css');
server.setRoute('/four-style.css', (req, res) => server.setRoute('/four-style.css', (_req, res) => {
res.end('body {box-sizing: border-box; }') return res.end('body {box-sizing: border-box; }');
); });
const response = await page.goto(server.PREFIX + '/one-style.html'); const response = (await page.goto(server.PREFIX + '/one-style.html'))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toContain('one-style.html'); expect(response.url()).toContain('one-style.html');
expect(requests.length).toBe(5); expect(requests.length).toBe(5);
expect(requests[0].resourceType()).toBe('document'); expect(requests[0]!.resourceType()).toBe('document');
expect(requests[1].resourceType()).toBe('stylesheet'); expect(requests[1]!.resourceType()).toBe('stylesheet');
// Check redirect chain // Check redirect chain
const redirectChain = requests[1].redirectChain(); const redirectChain = requests[1]!.redirectChain();
expect(redirectChain.length).toBe(3); expect(redirectChain.length).toBe(3);
expect(redirectChain[0].url()).toContain('/one-style.css'); expect(redirectChain[0]!.url()).toContain('/one-style.css');
expect(redirectChain[2].url()).toContain('/three-style.css'); expect(redirectChain[2]!.url()).toContain('/three-style.css');
}); });
it('should be able to abort redirects', async () => { it('should be able to abort redirects', async () => {
const { page, server, isChrome } = getTestState(); const { page, server, isChrome } = getTestState();
@ -331,9 +353,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const result = await page.evaluate(async () => { const result = await page.evaluate(async () => {
try { try {
await fetch('/non-existing.json'); return await fetch('/non-existing.json');
} catch (error) { } catch (error) {
return error.message; return (error as Error).message;
} }
}); });
if (isChrome) { if (isChrome) {
@ -347,7 +369,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let responseCount = 1; let responseCount = 1;
server.setRoute('/zzz', (req, res) => res.end(responseCount++ * 11 + '')); server.setRoute('/zzz', (_req, res) => {
return res.end(responseCount++ * 11 + '');
});
await page.setRequestInterception(true); await page.setRequestInterception(true);
let spinner = false; let spinner = false;
@ -360,69 +384,82 @@ describe('request interception', function () {
spinner ? request.abort() : request.continue(); spinner ? request.abort() : request.continue();
spinner = !spinner; spinner = !spinner;
}); });
const results = await page.evaluate(() => const results = await page.evaluate(() => {
Promise.all([ return Promise.all([
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response.text();
})
.catch(() => {
return 'FAILED';
}),
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response.text();
})
.catch(() => {
return 'FAILED';
}),
fetch('/zzz') fetch('/zzz')
.then((response) => response.text()) .then((response) => {
.catch(() => 'FAILED'), return response.text();
]) })
); .catch(() => {
return 'FAILED';
}),
]);
});
expect(results).toEqual(['11', 'FAILED', '22']); expect(results).toEqual(['11', 'FAILED', '22']);
}); });
it('should navigate to dataURL and fire dataURL requests', async () => { it('should navigate to dataURL and fire dataURL requests', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
requests.push(request); requests.push(request);
request.continue(); request.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const response = await page.goto(dataURL); const response = (await page.goto(dataURL))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL); expect(requests[0]!.url()).toBe(dataURL);
}); });
it('should be able to fetch dataURL and fire dataURL requests', async () => { it('should be able to fetch dataURL and fire dataURL requests', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
!utils.isFavicon(request) && requests.push(request); !utils.isFavicon(request) && requests.push(request);
request.continue(); request.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const text = await page.evaluate( const text = await page.evaluate((url: string) => {
(url: string) => fetch(url).then((r) => r.text()), return fetch(url).then((r) => {
dataURL return r.text();
); });
}, dataURL);
expect(text).toBe('<div>yo</div>'); expect(text).toBe('<div>yo</div>');
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL); expect(requests[0]!.url()).toBe(dataURL);
}); });
it('should navigate to URL with hash and fire requests without hash', async () => { it('should navigate to URL with hash and fire requests without hash', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
requests.push(request); requests.push(request);
request.continue(); request.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE + '#hash'); const response = (await page.goto(server.EMPTY_PAGE + '#hash'))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
}); });
it('should work with encoded server', async () => { it('should work with encoded server', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -430,19 +467,27 @@ describe('request interception', function () {
// The requestWillBeSent will report encoded URL, whereas interception will // The requestWillBeSent will report encoded URL, whereas interception will
// report URL as-is. @see crbug.com/759388 // report URL as-is. @see crbug.com/759388
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
const response = await page.goto( return request.continue();
});
const response = (await page.goto(
server.PREFIX + '/some nonexisting page' server.PREFIX + '/some nonexisting page'
); ))!;
expect(response.status()).toBe(404); expect(response.status()).toBe(404);
}); });
it('should work with badly encoded server', async () => { it('should work with badly encoded server', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); server.setRoute('/malformed?rnd=%911', (_req, res) => {
page.on('request', (request) => request.continue()); return res.end();
const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); });
page.on('request', (request) => {
return request.continue();
});
const response = (await page.goto(
server.PREFIX + '/malformed?rnd=%911'
))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it('should work with encoded server - 2', async () => { it('should work with encoded server - 2', async () => {
@ -451,47 +496,55 @@ describe('request interception', function () {
// The requestWillBeSent will report URL as-is, whereas interception will // The requestWillBeSent will report URL as-is, whereas interception will
// report encoded URL for stylesheet. @see crbug.com/759388 // report encoded URL for stylesheet. @see crbug.com/759388
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests: HTTPRequest[] = [];
page.on('request', (request) => { page.on('request', (request) => {
request.continue(); request.continue();
requests.push(request); requests.push(request);
}); });
const response = await page.goto( const response = (await page.goto(
`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>` `data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`
); ))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
expect(requests[1].response().status()).toBe(404); expect(requests[1]!.response()!.status()).toBe(404);
}); });
it('should not throw "Invalid Interception Id" if the request was cancelled', async () => { it('should not throw "Invalid Interception Id" if the request was cancelled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setContent('<iframe></iframe>'); await page.setContent('<iframe></iframe>');
await page.setRequestInterception(true); await page.setRequestInterception(true);
let request = null; let request!: HTTPRequest;
page.on('request', async (r) => (request = r)); page.on('request', async (r) => {
return (request = r);
});
page.$eval( page.$eval(
'iframe', 'iframe',
(frame: HTMLIFrameElement, url: string) => (frame.src = url), (frame, url) => {
return ((frame as HTMLIFrameElement).src = url as string);
},
server.EMPTY_PAGE server.EMPTY_PAGE
), ),
// Wait for request interception. // Wait for request interception.
await utils.waitEvent(page, 'request'); await utils.waitEvent(page, 'request');
// Delete frame to cause request to be canceled. // Delete frame to cause request to be canceled.
await page.$eval('iframe', (frame) => frame.remove()); await page.$eval('iframe', (frame) => {
let error = null; return frame.remove();
await request.continue().catch((error_) => (error = error_)); });
expect(error).toBe(null); let error!: Error;
await request.continue().catch((error_) => {
return (error = error_);
});
expect(error).toBeUndefined();
}); });
it('should throw if interception is not enabled', async () => { it('should throw if interception is not enabled', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error = null; let error!: Error;
page.on('request', async (request) => { page.on('request', async (request) => {
try { try {
await request.continue(); await request.continue();
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -521,10 +574,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(false); await page.setCacheEnabled(false);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
const cached = []; const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r)); page.on('requestservedfromcache', (r) => {
return cached.push(r);
});
await page.reload(); await page.reload();
expect(cached.length).toBe(0); expect(cached.length).toBe(0);
@ -537,10 +594,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(true); await page.setCacheEnabled(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
const cached = []; const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r)); page.on('requestservedfromcache', (r) => {
return cached.push(r);
});
await page.reload(); await page.reload();
expect(cached.length).toBe(1); expect(cached.length).toBe(1);
@ -550,10 +611,14 @@ describe('request interception', function () {
await page.setRequestInterception(true); await page.setRequestInterception(true);
await page.setCacheEnabled(true); await page.setCacheEnabled(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
await page.goto(server.PREFIX + '/cached/one-style-font.html'); await page.goto(server.PREFIX + '/cached/one-style-font.html');
await page.waitForResponse((r) => r.url().endsWith('/one-style.woff')); await page.waitForResponse((r) => {
return r.url().endsWith('/one-style.woff');
});
}); });
}); });
@ -562,7 +627,9 @@ describe('request interception', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => request.continue()); page.on('request', (request) => {
return request.continue();
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
}); });
it('should amend HTTP headers', async () => { it('should amend HTTP headers', async () => {
@ -577,7 +644,9 @@ describe('request interception', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')), page.evaluate(() => {
return fetch('/sleep.zzz');
}),
]); ]);
expect(request.headers['foo']).toBe('bar'); expect(request.headers['foo']).toBe('bar');
}); });
@ -591,8 +660,10 @@ describe('request interception', function () {
: undefined; : undefined;
request.continue({ url: redirectURL }); request.continue({ url: redirectURL });
}); });
let consoleMessage = null; let consoleMessage!: ConsoleMessage;
page.on('console', (msg) => (consoleMessage = msg)); page.on('console', (msg) => {
return (consoleMessage = msg);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(page.url()).toBe(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE);
expect(consoleMessage.text()).toBe('yellow'); expect(consoleMessage.text()).toBe('yellow');
@ -608,7 +679,9 @@ describe('request interception', function () {
}); });
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')), page.evaluate(() => {
return fetch('/sleep.zzz');
}),
]); ]);
expect(request.method).toBe('POST'); expect(request.method).toBe('POST');
}); });
@ -623,9 +696,9 @@ describe('request interception', function () {
}); });
const [serverRequest] = await Promise.all([ const [serverRequest] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => page.evaluate(() => {
fetch('/sleep.zzz', { method: 'POST', body: 'birdy' }) return fetch('/sleep.zzz', { method: 'POST', body: 'birdy' });
), }),
]); ]);
expect(await serverRequest.postBody).toBe('doggo'); expect(await serverRequest.postBody).toBe('doggo');
}); });
@ -646,7 +719,7 @@ describe('request interception', function () {
it('should fail if the header value is invalid', async () => { it('should fail if the header value is invalid', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error; let error!: Error;
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', async (request) => { page.on('request', async (request) => {
await request await request
@ -656,7 +729,7 @@ describe('request interception', function () {
}, },
}) })
.catch((error_) => { .catch((error_) => {
error = error_; error = error_ as Error;
}); });
await request.continue(); await request.continue();
}); });
@ -679,12 +752,14 @@ describe('request interception', function () {
body: 'Yo, page!', body: 'Yo, page!',
}); });
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(201); expect(response.status()).toBe(201);
expect(response.headers().foo).toBe('bar'); expect(response.headers()['foo']).toBe('bar');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should work with status code 422', async () => { it('should work with status code 422', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -696,12 +771,14 @@ describe('request interception', function () {
body: 'Yo, page!', body: 'Yo, page!',
}); });
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(422); expect(response.status()).toBe(422);
expect(response.statusText()).toBe('Unprocessable Entity'); expect(response.statusText()).toBe('Unprocessable Entity');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should redirect', async () => { it('should redirect', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -719,9 +796,9 @@ describe('request interception', function () {
}, },
}); });
}); });
const response = await page.goto(server.PREFIX + '/rrredirect'); const response = (await page.goto(server.PREFIX + '/rrredirect'))!;
expect(response.request().redirectChain().length).toBe(1); expect(response.request().redirectChain().length).toBe(1);
expect(response.request().redirectChain()[0].url()).toBe( expect(response.request().redirectChain()[0]!.url()).toBe(
server.PREFIX + '/rrredirect' server.PREFIX + '/rrredirect'
); );
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);
@ -741,13 +818,17 @@ describe('request interception', function () {
body: 'Hello world', body: 'Hello world',
}); });
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
const cookies = await page.cookies(); const cookies = await page.cookies();
const firstCookie = cookies.find((cookie) => cookie.name === 'first'); const firstCookie = cookies.find((cookie) => {
const secondCookie = cookies.find((cookie) => cookie.name === 'second'); return cookie.name === 'first';
});
const secondCookie = cookies.find((cookie) => {
return cookie.name === 'second';
});
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.headers().foo).toBe('bar'); expect(response.headers()['foo']).toBe('bar');
expect(response.headers().arr).toBe('1\n2'); expect(response.headers()['arr']).toBe('1\n2');
// request.respond() will not trigger Network.responseReceivedExtraInfo // request.respond() will not trigger Network.responseReceivedExtraInfo
// fail to get 'set-cookie' header from response // fail to get 'set-cookie' header from response
expect(firstCookie?.value).toBe('1'); expect(firstCookie?.value).toBe('1');
@ -770,9 +851,11 @@ describe('request interception', function () {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = PREFIX + '/does-not-exist.png'; img.src = PREFIX + '/does-not-exist.png';
document.body.appendChild(img); document.body.appendChild(img);
return new Promise((fulfill) => (img.onload = fulfill)); return new Promise((fulfill) => {
return (img.onload = fulfill);
});
}, server.PREFIX); }, server.PREFIX);
const img = await page.$('img'); const img = (await page.$('img'))!;
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
}); });
it('should stringify intercepted request response headers', async () => { it('should stringify intercepted request response headers', async () => {
@ -788,18 +871,20 @@ describe('request interception', function () {
body: 'Yo, page!', body: 'Yo, page!',
}); });
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
const headers = response.headers(); const headers = response.headers();
expect(headers.foo).toBe('true'); expect(headers['foo']).toBe('true');
expect(await page.evaluate(() => document.body.textContent)).toBe( expect(
'Yo, page!' await page.evaluate(() => {
); return document.body.textContent;
})
).toBe('Yo, page!');
}); });
it('should fail if the header value is invalid', async () => { it('should fail if the header value is invalid', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let error; let error!: Error;
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', async (request) => { page.on('request', async (request) => {
await request await request
@ -809,7 +894,7 @@ describe('request interception', function () {
}, },
}) })
.catch((error_) => { .catch((error_) => {
error = error_; error = error_ as Error;
}); });
await request.respond({ await request.respond({
status: 200, status: 200,

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describe('Screenshots', function () { describe('Screenshots', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -86,7 +86,7 @@ describe('Screenshots', function () {
); );
} }
const screenshots = await Promise.all(promises); const screenshots = await Promise.all(promises);
expect(screenshots[1]).toBeGolden('grid-cell-1.png'); expect(screenshots[1]!).toBeGolden('grid-cell-1.png');
}); });
itFailsFirefox('should take fullPage screenshots', async () => { itFailsFirefox('should take fullPage screenshots', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -114,7 +114,7 @@ describe('Screenshots', function () {
const promises = []; const promises = [];
for (let i = 0; i < N; ++i) { for (let i = 0; i < N; ++i) {
promises.push( promises.push(
pages[i].screenshot({ pages[i]!.screenshot({
clip: { x: 50 * i, y: 0, width: 50, height: 50 }, clip: { x: 50 * i, y: 0, width: 50, height: 50 },
}) })
); );
@ -123,7 +123,11 @@ describe('Screenshots', function () {
for (let i = 0; i < N; ++i) { for (let i = 0; i < N; ++i) {
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`); expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
} }
await Promise.all(pages.map((page) => page.close())); await Promise.all(
pages.map((page) => {
return page.close();
})
);
}); });
itFailsFirefox('should allow transparency', async () => { itFailsFirefox('should allow transparency', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -192,8 +196,10 @@ describe('Screenshots', function () {
await page.setViewport({ width: 500, height: 500 }); await page.setViewport({ width: 500, height: 500 });
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100)); await page.evaluate(() => {
const elementHandle = await page.$('.box:nth-of-type(3)'); return window.scrollBy(50, 100);
});
const elementHandle = (await page.$('.box:nth-of-type(3)'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png'); expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
}); });
@ -212,7 +218,7 @@ describe('Screenshots', function () {
</style> </style>
<div></div> <div></div>
`); `);
const elementHandle = await page.$('div'); const elementHandle = (await page.$('div'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-padding-border.png'); expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
}); });
@ -238,17 +244,19 @@ describe('Screenshots', function () {
</style> </style>
<div class="to-screenshot"></div> <div class="to-screenshot"></div>
`); `);
const elementHandle = await page.$('div.to-screenshot'); const elementHandle = (await page.$('div.to-screenshot'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden( expect(screenshot).toBeGolden(
'screenshot-element-larger-than-viewport.png' 'screenshot-element-larger-than-viewport.png'
); );
expect( expect(
await page.evaluate(() => ({ await page.evaluate(() => {
return {
w: window.innerWidth, w: window.innerWidth,
h: window.innerHeight, h: window.innerHeight,
})) };
})
).toEqual({ w: 500, h: 500 }); ).toEqual({ w: 500, h: 500 });
} }
); );
@ -273,7 +281,7 @@ describe('Screenshots', function () {
<div class="above"></div> <div class="above"></div>
<div class="to-screenshot"></div> <div class="to-screenshot"></div>
`); `);
const elementHandle = await page.$('div.to-screenshot'); const elementHandle = (await page.$('div.to-screenshot'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden( expect(screenshot).toBeGolden(
'screenshot-element-scrolled-into-view.png' 'screenshot-element-scrolled-into-view.png'
@ -290,7 +298,7 @@ describe('Screenshots', function () {
height: 100px; height: 100px;
background: green; background: green;
transform: rotateZ(200deg);">&nbsp;</div>`); transform: rotateZ(200deg);">&nbsp;</div>`);
const elementHandle = await page.$('div'); const elementHandle = (await page.$('div'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-rotate.png'); expect(screenshot).toBeGolden('screenshot-element-rotate.png');
}); });
@ -298,14 +306,15 @@ describe('Screenshots', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<h1>remove this</h1>'); await page.setContent('<h1>remove this</h1>');
const elementHandle = await page.$('h1'); const elementHandle = (await page.$('h1'))!;
await page.evaluate( await page.evaluate((element: HTMLElement) => {
(element: HTMLElement) => element.remove(), return element.remove();
elementHandle }, elementHandle);
);
const screenshotError = await elementHandle const screenshotError = await elementHandle
.screenshot() .screenshot()
.catch((error) => error); .catch((error) => {
return error;
});
expect(screenshotError.message).toBe( expect(screenshotError.message).toBe(
'Node is either not visible or not an HTMLElement' 'Node is either not visible or not an HTMLElement'
); );
@ -314,8 +323,10 @@ describe('Screenshots', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<div style="width: 50px; height: 0"></div>'); await page.setContent('<div style="width: 50px; height: 0"></div>');
const div = await page.$('div'); const div = (await page.$('div'))!;
const error = await div.screenshot().catch((error_) => error_); const error = await div.screenshot().catch((error_) => {
return error_;
});
expect(error.message).toBe('Node has 0 height.'); expect(error.message).toBe('Node has 0 height.');
}); });
it('should work for an element with fractional dimensions', async () => { it('should work for an element with fractional dimensions', async () => {
@ -324,7 +335,7 @@ describe('Screenshots', function () {
await page.setContent( await page.setContent(
'<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>' '<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>'
); );
const elementHandle = await page.$('div'); const elementHandle = (await page.$('div'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional.png'); expect(screenshot).toBeGolden('screenshot-element-fractional.png');
}); });
@ -334,7 +345,7 @@ describe('Screenshots', function () {
await page.setContent( await page.setContent(
'<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>' '<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>'
); );
const elementHandle = await page.$('div'); const elementHandle = (await page.$('div'))!;
const screenshot = await elementHandle.screenshot(); const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png'); expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
}); });

View File

@ -14,16 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import utils from './utils.js';
const { waitEvent } = utils;
import expect from 'expect'; import expect from 'expect';
import { ServerResponse } from 'http';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { Target } from '../../lib/cjs/puppeteer/common/Target.js';
import { import {
getTestState, getTestState,
itFailsFirefox,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, } from './mocha-utils.js';
} from './mocha-utils'; // eslint-disable-line import/extensions import utils from './utils.js';
import { Target } from '../../lib/cjs/puppeteer/common/Target.js'; const { waitEvent } = utils;
describe('Target', function () { describe('Target', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -35,11 +37,15 @@ describe('Target', function () {
// The pages will be the testing page and the original newtab page // The pages will be the testing page and the original newtab page
const targets = browser.targets(); const targets = browser.targets();
expect( expect(
targets.some( targets.some((target) => {
(target) => target.type() === 'page' && target.url() === 'about:blank' return target.type() === 'page' && target.url() === 'about:blank';
) })
).toBeTruthy();
expect(
targets.some((target) => {
return target.type() === 'browser';
})
).toBeTruthy(); ).toBeTruthy();
expect(targets.some((target) => target.type() === 'browser')).toBeTruthy();
}); });
it('Browser.pages should return all of the pages', async () => { it('Browser.pages should return all of the pages', async () => {
const { page, context } = getTestState(); const { page, context } = getTestState();
@ -53,7 +59,9 @@ describe('Target', function () {
const { browser } = getTestState(); const { browser } = getTestState();
const targets = browser.targets(); const targets = browser.targets();
const browserTarget = targets.find((target) => target.type() === 'browser'); const browserTarget = targets.find((target) => {
return target.type() === 'browser';
});
expect(browserTarget).toBeTruthy(); expect(browserTarget).toBeTruthy();
}); });
it('should be able to use the default page in the browser', async () => { it('should be able to use the default page in the browser', async () => {
@ -61,9 +69,13 @@ describe('Target', function () {
// The pages will be the testing page and the original newtab page // The pages will be the testing page and the original newtab page
const allPages = await browser.pages(); const allPages = await browser.pages();
const originalPage = allPages.find((p) => p !== page); const originalPage = allPages.find((p) => {
return p !== page;
})!;
expect( expect(
await originalPage.evaluate(() => ['Hello', 'world'].join(' ')) await originalPage.evaluate(() => {
return ['Hello', 'world'].join(' ');
})
).toBe('Hello world'); ).toBe('Hello world');
expect(await originalPage.$('body')).toBeTruthy(); expect(await originalPage.$('body')).toBeTruthy();
}); });
@ -72,21 +84,19 @@ describe('Target', function () {
const [otherPage] = await Promise.all([ const [otherPage] = await Promise.all([
context context
.waitForTarget((target) => .waitForTarget((target) => {
target return target.page().then((page) => {
.page() return page!.url() === server.CROSS_PROCESS_PREFIX + '/empty.html';
.then( });
(page) => })
page.url() === server.CROSS_PROCESS_PREFIX + '/empty.html' .then((target) => {
) return target.page();
) }),
.then((target) => target.page()), page.evaluate((url: string) => {
page.evaluate( return window.open(url);
(url: string) => window.open(url), }, server.CROSS_PROCESS_PREFIX + '/empty.html'),
server.CROSS_PROCESS_PREFIX + '/empty.html'
),
]); ]);
expect(otherPage.url()).toEqual( expect(otherPage!.url()).toEqual(
server.CROSS_PROCESS_PREFIX + '/empty.html' server.CROSS_PROCESS_PREFIX + '/empty.html'
); );
expect(page).not.toEqual(otherPage); expect(page).not.toEqual(otherPage);
@ -98,35 +108,41 @@ describe('Target', function () {
const [otherPage] = await Promise.all([ const [otherPage] = await Promise.all([
context context
.waitForTarget( .waitForTarget((target) => {
(target) => return target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html';
target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html' })
) .then((target) => {
.then((target) => target.page()), return target.page();
page.evaluate( }),
(url: string) => window.open(url), page.evaluate((url: string) => {
server.CROSS_PROCESS_PREFIX + '/empty.html' return window.open(url);
), }, server.CROSS_PROCESS_PREFIX + '/empty.html'),
]); ]);
expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); expect(otherPage!.url()).toContain(server.CROSS_PROCESS_PREFIX);
expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe( expect(
'Hello world' await otherPage!.evaluate(() => {
); return ['Hello', 'world'].join(' ');
expect(await otherPage.$('body')).toBeTruthy(); })
).toBe('Hello world');
expect(await otherPage!.$('body')).toBeTruthy();
let allPages = await context.pages(); let allPages = await context.pages();
expect(allPages).toContain(page); expect(allPages).toContain(page);
expect(allPages).toContain(otherPage); expect(allPages).toContain(otherPage);
const closePagePromise = new Promise((fulfill) => const closePagePromise = new Promise((fulfill) => {
context.once('targetdestroyed', (target) => fulfill(target.page())) return context.once('targetdestroyed', (target) => {
); return fulfill(target.page());
await otherPage.close(); });
});
await otherPage!.close();
expect(await closePagePromise).toBe(otherPage); expect(await closePagePromise).toBe(otherPage);
allPages = await Promise.all( allPages = (await Promise.all(
context.targets().map((target) => target.page()) context.targets().map((target) => {
); return target.page();
})
)) as Page[];
expect(allPages).toContain(page); expect(allPages).toContain(page);
expect(allPages).not.toContain(otherPage); expect(allPages).not.toContain(otherPage);
} }
@ -137,9 +153,11 @@ describe('Target', function () {
const { page, server, context } = getTestState(); const { page, server, context } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const createdTarget = new Promise<Target>((fulfill) => const createdTarget = new Promise<Target>((fulfill) => {
context.once('targetcreated', (target) => fulfill(target)) return context.once('targetcreated', (target) => {
); return fulfill(target);
});
});
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
@ -148,14 +166,18 @@ describe('Target', function () {
server.PREFIX + '/serviceworkers/empty/sw.js' server.PREFIX + '/serviceworkers/empty/sw.js'
); );
const destroyedTarget = new Promise((fulfill) => const destroyedTarget = new Promise((fulfill) => {
context.once('targetdestroyed', (target) => fulfill(target)) return context.once('targetdestroyed', (target) => {
); return fulfill(target);
await page.evaluate(() => });
globalThis.registrationPromise.then((registration) => });
registration.unregister() await page.evaluate(() => {
) return (globalThis as any).registrationPromise.then(
(registration: { unregister: () => any }) => {
return registration.unregister();
}
); );
});
expect(await destroyedTarget).toBe(await createdTarget); expect(await destroyedTarget).toBe(await createdTarget);
} }
); );
@ -164,13 +186,15 @@ describe('Target', function () {
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
const target = await context.waitForTarget( const target = await context.waitForTarget((target) => {
(target) => target.type() === 'service_worker' return target.type() === 'service_worker';
); });
const worker = await target.worker(); const worker = (await target.worker())!;
expect(await worker.evaluate(() => self.toString())).toBe( expect(
'[object ServiceWorkerGlobalScope]' await worker.evaluate(() => {
); return self.toString();
})
).toBe('[object ServiceWorkerGlobalScope]');
}); });
itFailsFirefox('should create a worker from a shared worker', async () => { itFailsFirefox('should create a worker from a shared worker', async () => {
const { page, server, context } = getTestState(); const { page, server, context } = getTestState();
@ -179,27 +203,33 @@ describe('Target', function () {
await page.evaluate(() => { await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")'); new SharedWorker('data:text/javascript,console.log("hi")');
}); });
const target = await context.waitForTarget( const target = await context.waitForTarget((target) => {
(target) => target.type() === 'shared_worker' return target.type() === 'shared_worker';
); });
const worker = await target.worker(); const worker = (await target.worker())!;
expect(await worker.evaluate(() => self.toString())).toBe( expect(
'[object SharedWorkerGlobalScope]' await worker.evaluate(() => {
); return self.toString();
})
).toBe('[object SharedWorkerGlobalScope]');
}); });
itFailsFirefox('should report when a target url changes', async () => { itFailsFirefox('should report when a target url changes', async () => {
const { page, server, context } = getTestState(); const { page, server, context } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let changedTarget = new Promise<Target>((fulfill) => let changedTarget = new Promise<Target>((fulfill) => {
context.once('targetchanged', (target) => fulfill(target)) return context.once('targetchanged', (target) => {
); return fulfill(target);
});
});
await page.goto(server.CROSS_PROCESS_PREFIX + '/'); await page.goto(server.CROSS_PROCESS_PREFIX + '/');
expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/'); expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
changedTarget = new Promise((fulfill) => changedTarget = new Promise((fulfill) => {
context.once('targetchanged', (target) => fulfill(target)) return context.once('targetchanged', (target) => {
); return fulfill(target);
});
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect((await changedTarget).url()).toBe(server.EMPTY_PAGE); expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
}); });
@ -207,20 +237,28 @@ describe('Target', function () {
const { context } = getTestState(); const { context } = getTestState();
let targetChanged = false; let targetChanged = false;
const listener = () => (targetChanged = true); const listener = () => {
return (targetChanged = true);
};
context.on('targetchanged', listener); context.on('targetchanged', listener);
const targetPromise = new Promise<Target>((fulfill) => const targetPromise = new Promise<Target>((fulfill) => {
context.once('targetcreated', (target) => fulfill(target)) return context.once('targetcreated', (target) => {
); return fulfill(target);
});
});
const newPagePromise = context.newPage(); const newPagePromise = context.newPage();
const target = await targetPromise; const target = await targetPromise;
expect(target.url()).toBe('about:blank'); expect(target.url()).toBe('about:blank');
const newPage = await newPagePromise; const newPage = await newPagePromise;
const targetPromise2 = new Promise<Target>((fulfill) => const targetPromise2 = new Promise<Target>((fulfill) => {
context.once('targetcreated', (target) => fulfill(target)) return context.once('targetcreated', (target) => {
); return fulfill(target);
const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); });
});
const evaluatePromise = newPage.evaluate(() => {
return window.open('about:blank');
});
const target2 = await targetPromise2; const target2 = await targetPromise2;
expect(target2.url()).toBe('about:blank'); expect(target2.url()).toBe('about:blank');
await evaluatePromise; await evaluatePromise;
@ -233,21 +271,22 @@ describe('Target', function () {
async () => { async () => {
const { page, server, context } = getTestState(); const { page, server, context } = getTestState();
let serverResponse = null; let serverResponse!: ServerResponse;
server.setRoute('/one-style.css', (req, res) => (serverResponse = res)); server.setRoute('/one-style.css', (_req, res) => {
return (serverResponse = res);
});
// Open a new page. Use window.open to connect to the page later. // Open a new page. Use window.open to connect to the page later.
await Promise.all([ await Promise.all([
page.evaluate( page.evaluate((url: string) => {
(url: string) => window.open(url), return window.open(url);
server.PREFIX + '/one-style.html' }, server.PREFIX + '/one-style.html'),
),
server.waitForRequest('/one-style.css'), server.waitForRequest('/one-style.css'),
]); ]);
// Connect to the opened page. // Connect to the opened page.
const target = await context.waitForTarget((target) => const target = await context.waitForTarget((target) => {
target.url().includes('one-style.html') return target.url().includes('one-style.html');
); });
const newPage = await target.page(); const newPage = (await target.page())!;
// Issue a redirect. // Issue a redirect.
serverResponse.writeHead(302, { location: '/injectedstyle.css' }); serverResponse.writeHead(302, { location: '/injectedstyle.css' });
serverResponse.end(); serverResponse.end();
@ -262,12 +301,14 @@ describe('Target', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [createdTarget] = await Promise.all([ const [createdTarget] = await Promise.all([
new Promise<Target>((fulfill) => new Promise<Target>((fulfill) => {
context.once('targetcreated', (target) => fulfill(target)) return context.once('targetcreated', (target) => {
), return fulfill(target);
});
}),
page.goto(server.PREFIX + '/popup/window-open.html'), page.goto(server.PREFIX + '/popup/window-open.html'),
]); ]);
expect((await createdTarget.page()).url()).toBe( expect((await createdTarget.page())!.url()).toBe(
server.PREFIX + '/popup/popup.html' server.PREFIX + '/popup/popup.html'
); );
expect(createdTarget.opener()).toBe(page.target()); expect(createdTarget.opener()).toBe(page.target());
@ -279,11 +320,13 @@ describe('Target', function () {
const { browser, puppeteer, server } = getTestState(); const { browser, puppeteer, server } = getTestState();
let resolved = false; let resolved = false;
const targetPromise = browser.waitForTarget( const targetPromise = browser.waitForTarget((target) => {
(target) => target.url() === server.EMPTY_PAGE return target.url() === server.EMPTY_PAGE;
); });
targetPromise targetPromise
.then(() => (resolved = true)) .then(() => {
return (resolved = true);
})
.catch((error) => { .catch((error) => {
resolved = true; resolved = true;
if (error instanceof puppeteer.errors.TimeoutError) { if (error instanceof puppeteer.errors.TimeoutError) {
@ -310,12 +353,19 @@ describe('Target', function () {
it('should timeout waiting for a non-existent target', async () => { it('should timeout waiting for a non-existent target', async () => {
const { browser, server, puppeteer } = getTestState(); const { browser, server, puppeteer } = getTestState();
let error = null; let error!: Error;
await browser await browser
.waitForTarget((target) => target.url() === server.EMPTY_PAGE, { .waitForTarget(
(target) => {
return target.url() === server.EMPTY_PAGE;
},
{
timeout: 1, timeout: 1,
}) }
.catch((error_) => (error = error_)); )
.catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
}); });

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils.js';
describeFailsFirefox('Touchscreen', function () { describeFailsFirefox('Touchscreen', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -28,22 +28,27 @@ describeFailsFirefox('Touchscreen', function () {
it('should tap the button', async () => { it('should tap the button', async () => {
const { puppeteer, page, server } = getTestState(); const { puppeteer, page, server } = getTestState();
const iPhone = puppeteer.devices['iPhone 6']; const iPhone = puppeteer.devices['iPhone 6']!;
await page.emulate(iPhone); await page.emulate(iPhone);
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.tap('button'); await page.tap('button');
expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); expect(
await page.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
}); });
it('should report touches', async () => { it('should report touches', async () => {
const { puppeteer, page, server } = getTestState(); const { puppeteer, page, server } = getTestState();
const iPhone = puppeteer.devices['iPhone 6']; const iPhone = puppeteer.devices['iPhone 6']!;
await page.emulate(iPhone); await page.emulate(iPhone);
await page.goto(server.PREFIX + '/input/touches.html'); await page.goto(server.PREFIX + '/input/touches.html');
const button = await page.$('button'); const button = (await page.$('button'))!;
await button.tap(); await button.tap();
expect(await page.evaluate(() => globalThis.getResult())).toEqual([ expect(
'Touchstart: 0', await page.evaluate(() => {
'Touchend: 0', return (globalThis as any).getResult();
]); })
).toEqual(['Touchstart: 0', 'Touchend: 0']);
}); });
}); });

View File

@ -17,12 +17,14 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import expect from 'expect'; import expect from 'expect';
import { getTestState, describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions import { getTestState, describeChromeOnly } from './mocha-utils.js';
import { Browser } from '../../lib/cjs/puppeteer/common/Browser.js';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
describeChromeOnly('Tracing', function () { describeChromeOnly('Tracing', function () {
let outputFile; let outputFile!: string;
let browser; let browser!: Browser;
let page; let page!: Page;
/* we manually manage the browser here as we want a new browser for each /* we manually manage the browser here as we want a new browser for each
* individual test, which isn't the default behaviour of getTestState() * individual test, which isn't the default behaviour of getTestState()
@ -37,11 +39,8 @@ describeChromeOnly('Tracing', function () {
afterEach(async () => { afterEach(async () => {
await browser.close(); await browser.close();
browser = null;
page = null;
if (fs.existsSync(outputFile)) { if (fs.existsSync(outputFile)) {
fs.unlinkSync(outputFile); fs.unlinkSync(outputFile);
outputFile = null;
} }
}); });
it('should output a trace', async () => { it('should output a trace', async () => {
@ -93,10 +92,10 @@ describeChromeOnly('Tracing', function () {
it('should throw if tracing on two pages', async () => { it('should throw if tracing on two pages', async () => {
await page.tracing.start({ path: outputFile }); await page.tracing.start({ path: outputFile });
const newPage = await browser.newPage(); const newPage = await browser.newPage();
let error = null; let error!: Error;
await newPage.tracing await newPage.tracing.start({ path: outputFile }).catch((error_) => {
.start({ path: outputFile }) return (error = error_);
.catch((error_) => (error = error_)); });
await newPage.close(); await newPage.close();
expect(error).toBeTruthy(); expect(error).toBeTruthy();
await page.tracing.stop(); await page.tracing.stop();
@ -106,7 +105,7 @@ describeChromeOnly('Tracing', function () {
await page.tracing.start({ screenshots: true, path: outputFile }); await page.tracing.start({ screenshots: true, path: outputFile });
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const trace = await page.tracing.stop(); const trace = (await page.tracing.stop())!;
const buf = fs.readFileSync(outputFile); const buf = fs.readFileSync(outputFile);
expect(trace.toString()).toEqual(buf.toString()); expect(trace.toString()).toEqual(buf.toString());
}); });
@ -138,18 +137,18 @@ describeChromeOnly('Tracing', function () {
await page.tracing.start({ screenshots: true }); await page.tracing.start({ screenshots: true });
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
const trace = await page.tracing.stop(); const trace = (await page.tracing.stop())!;
expect(trace.toString()).toContain('screenshot'); expect(trace.toString()).toContain('screenshot');
}); });
it('should properly fail if readProtocolStream errors out', async () => { it('should properly fail if readProtocolStream errors out', async () => {
await page.tracing.start({ path: __dirname }); await page.tracing.start({ path: __dirname });
let error: Error = null; let error!: Error;
try { try {
await page.tracing.stop(); await page.tracing.stop();
} catch (error_) { } catch (error_) {
error = error_; error = error_ as Error;
} }
expect(error).toBeDefined(); expect(error).toBeDefined();
}); });

View File

@ -1,137 +0,0 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO (@jackfranklin): convert to TS and enable type checking.
// @ts-nocheck
const path = require('path');
const expect = require('expect');
const GoldenUtils = require('./golden-utils.js');
const PROJECT_ROOT = path.join(__dirname, '..', '..');
const utils = (module.exports = {
extendExpectWithToBeGolden: function (goldenDir, outputDir) {
expect.extend({
toBeGolden: (testScreenshot, goldenFilePath) => {
const result = GoldenUtils.compare(
goldenDir,
outputDir,
testScreenshot,
goldenFilePath
);
return {
message: () => result.message,
pass: result.pass,
};
},
});
},
/**
* @returns {string}
*/
projectRoot: function () {
return PROJECT_ROOT;
},
/**
* @param {!Page} page
* @param {string} frameId
* @param {string} url
* @returns {!Frame}
*/
attachFrame: async function (page, frameId, url) {
const handle = await page.evaluateHandle(attachFrame, frameId, url);
return await handle.asElement().contentFrame();
async function attachFrame(frameId, url) {
const frame = document.createElement('iframe');
frame.src = url;
frame.id = frameId;
document.body.appendChild(frame);
await new Promise((x) => (frame.onload = x));
return frame;
}
},
isFavicon: function (request) {
return request.url().includes('favicon.ico');
},
/**
* @param {!Page} page
* @param {string} frameId
*/
detachFrame: async function (page, frameId) {
await page.evaluate(detachFrame, frameId);
function detachFrame(frameId) {
const frame = document.getElementById(frameId);
frame.remove();
}
},
/**
* @param {!Page} page
* @param {string} frameId
* @param {string} url
*/
navigateFrame: async function (page, frameId, url) {
await page.evaluate(navigateFrame, frameId, url);
function navigateFrame(frameId, url) {
const frame = document.getElementById(frameId);
frame.src = url;
return new Promise((x) => (frame.onload = x));
}
},
/**
* @param {!Frame} frame
* @param {string=} indentation
* @returns {Array<string>}
*/
dumpFrames: function (frame, indentation) {
indentation = indentation || '';
let description = frame.url().replace(/:\d{4}\//, ':<PORT>/');
if (frame.name()) {
description += ' (' + frame.name() + ')';
}
const result = [indentation + description];
for (const child of frame.childFrames()) {
result.push(...utils.dumpFrames(child, ' ' + indentation));
}
return result;
},
/**
* @param {!EventEmitter} emitter
* @param {string} eventName
* @returns {!Promise<!Object>}
*/
waitEvent: function (emitter, eventName, predicate = () => true) {
return new Promise((fulfill) => {
emitter.on(eventName, function listener(event) {
if (!predicate(event)) {
return;
}
emitter.removeListener(eventName, listener);
fulfill(event);
});
});
},
});

162
test/src/utils.ts Normal file
View File

@ -0,0 +1,162 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import expect from 'expect';
import path from 'path';
import { Frame } from '../../lib/cjs/puppeteer/common/FrameManager.js';
import { Page } from '../../lib/cjs/puppeteer/common/Page.js';
import { EventEmitter } from '../../lib/cjs/puppeteer/common/EventEmitter.js';
import { compare } from './golden-utils.js';
const PROJECT_ROOT = path.join(__dirname, '..', '..');
export const extendExpectWithToBeGolden = (
goldenDir: string,
outputDir: string
): void => {
expect.extend({
toBeGolden: (testScreenshot: string | Buffer, goldenFilePath: string) => {
const result = compare(
goldenDir,
outputDir,
testScreenshot,
goldenFilePath
);
if (result.pass) {
return {
pass: true,
message: () => {
return void 0;
},
};
} else {
return {
pass: false,
message: () => {
return result.message;
},
};
}
},
});
};
export const projectRoot = (): string => {
return PROJECT_ROOT;
};
export const attachFrame = async (
pageOrFrame: Page | Frame,
frameId: string,
url: string
): Promise<Frame | undefined> => {
const handle = await pageOrFrame.evaluateHandle(attachFrame, frameId, url);
return (await handle.asElement()?.contentFrame()) ?? undefined;
async function attachFrame(frameId: string, url: string) {
const frame = document.createElement('iframe');
frame.src = url;
frame.id = frameId;
document.body.appendChild(frame);
await new Promise((x) => {
return (frame.onload = x);
});
return frame;
}
};
export const isFavicon = (request: {
url: () => string | string[];
}): boolean => {
return request.url().includes('favicon.ico');
};
export async function detachFrame(
pageOrFrame: Page | Frame,
frameId: string
): Promise<void> {
await pageOrFrame.evaluate(detachFrame, frameId);
function detachFrame(frameId: string) {
const frame = document.getElementById(frameId) as HTMLIFrameElement;
frame.remove();
}
}
export async function navigateFrame(
pageOrFrame: Page | Frame,
frameId: string,
url: string
): Promise<void> {
await pageOrFrame.evaluate(navigateFrame, frameId, url);
function navigateFrame(frameId: string, url: any) {
const frame = document.getElementById(frameId) as HTMLIFrameElement;
frame.src = url;
return new Promise((x) => {
return (frame.onload = x);
});
}
}
export const dumpFrames = (
frame: Frame,
indentation?: string
): Array<string> => {
indentation = indentation || '';
let description = frame.url().replace(/:\d{4}\//, ':<PORT>/');
if (frame.name()) {
description += ' (' + frame.name() + ')';
}
const result = [indentation + description];
for (const child of frame.childFrames()) {
result.push(...dumpFrames(child, ' ' + indentation));
}
return result;
};
export const waitEvent = (
emitter: EventEmitter,
eventName: string,
predicate: (event: any) => boolean = () => {
return true;
}
): Promise<any> => {
return new Promise((fulfill) => {
emitter.on(eventName, function listener(event: any) {
if (!predicate(event)) {
return;
}
emitter.off(eventName, listener);
fulfill(event);
});
});
};
/**
* @deprecated Use exports directly.
*/
export default {
extendExpectWithToBeGolden,
waitEvent,
dumpFrames,
navigateFrame,
isFavicon,
attachFrame,
projectRoot,
detachFrame,
};

View File

@ -14,15 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
import utils from './utils.js';
import sinon from 'sinon';
import expect from 'expect'; import expect from 'expect';
import sinon from 'sinon';
import { isErrorLike } from '../../lib/cjs/puppeteer/common/util.js';
import { import {
getTestState, getTestState,
itFailsFirefox,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, } from './mocha-utils.js';
} from './mocha-utils'; // eslint-disable-line import/extensions import { attachFrame, detachFrame } from './utils.js';
describe('waittask specs', function () { describe('waittask specs', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -33,13 +34,17 @@ describe('waittask specs', function () {
* tests. Until we remove this method we still want to ensure we don't break * tests. Until we remove this method we still want to ensure we don't break
* it. * it.
*/ */
beforeEach(() => sinon.stub(console, 'warn').callsFake(() => {})); beforeEach(() => {
return sinon.stub(console, 'warn').callsFake(() => {});
});
it('should wait for selector', async () => { it('should wait for selector', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
let found = false; let found = false;
const waitFor = page.waitFor('div').then(() => (found = true)); const waitFor = page.waitForSelector('div').then(() => {
return (found = true);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false); expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
@ -51,7 +56,9 @@ describe('waittask specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
let found = false; let found = false;
const waitFor = page.waitFor('//div').then(() => (found = true)); const waitFor = page.waitFor('//div').then(() => {
return (found = true);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false); expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
@ -74,8 +81,10 @@ describe('waittask specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<div>some text</div>`); await page.setContent(`<div>some text</div>`);
let error = null; let error!: Error;
await page.waitFor('/html/body/div').catch((error_) => (error = error_)); await page.waitFor('/html/body/div').catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
}); });
it('should timeout', async () => { it('should timeout', async () => {
@ -98,28 +107,31 @@ describe('waittask specs', function () {
const { page } = getTestState(); const { page } = getTestState();
await Promise.all([ await Promise.all([
page.waitFor(() => window.innerWidth < 100), page.waitFor(() => {
return window.innerWidth < 100;
}),
page.setViewport({ width: 10, height: 10 }), page.setViewport({ width: 10, height: 10 }),
]); ]);
}); });
it('should throw when unknown type', async () => {
const { page } = getTestState();
let error = null;
// @ts-expect-error purposefully passing bad type for test
await page.waitFor({ foo: 'bar' }).catch((error_) => (error = error_));
expect(error.message).toContain('Unsupported target type');
});
it('should wait for predicate with arguments', async () => { it('should wait for predicate with arguments', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.waitFor((arg1, arg2) => arg1 !== arg2, {}, 1, 2); await page.waitFor(
(arg1: number, arg2: number) => {
return arg1 !== arg2;
},
{},
1,
2
);
}); });
it('should log a deprecation warning', async () => { it('should log a deprecation warning', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.waitFor(() => true); await page.waitFor(() => {
return true;
});
const consoleWarnStub = console.warn as sinon.SinonSpy; const consoleWarnStub = console.warn as sinon.SinonSpy;
@ -138,15 +150,19 @@ describe('waittask specs', function () {
const { page } = getTestState(); const { page } = getTestState();
const watchdog = page.waitForFunction('window.__FOO === 1'); const watchdog = page.waitForFunction('window.__FOO === 1');
await page.evaluate(() => (globalThis.__FOO = 1)); await page.evaluate(() => {
return ((globalThis as any).__FOO = 1);
});
await watchdog; await watchdog;
}); });
it('should work when resolved right before execution context disposal', async () => { it('should work when resolved right before execution context disposal', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true)); await page.evaluateOnNewDocument(() => {
return ((globalThis as any).__RELOADED = true);
});
await page.waitForFunction(() => { await page.waitForFunction(() => {
if (!globalThis.__RELOADED) { if (!(globalThis as any).__RELOADED) {
window.location.reload(); window.location.reload();
} }
return true; return true;
@ -159,13 +175,24 @@ describe('waittask specs', function () {
const startTime = Date.now(); const startTime = Date.now();
const polling = 100; const polling = 100;
const watchdog = page const watchdog = page
.waitForFunction(() => globalThis.__FOO === 'hit', { polling }) .waitForFunction(
.then(() => (success = true)); () => {
await page.evaluate(() => (globalThis.__FOO = 'hit')); return (globalThis as any).__FOO === 'hit';
},
{
polling,
}
)
.then(() => {
return (success = true);
});
await page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
});
expect(success).toBe(false); expect(success).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.body.appendChild(document.createElement('div')) return document.body.appendChild(document.createElement('div'));
); });
await watchdog; await watchdog;
expect(Date.now() - startTime).not.toBeLessThan(polling / 2); expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
}); });
@ -175,13 +202,24 @@ describe('waittask specs', function () {
const startTime = Date.now(); const startTime = Date.now();
const polling = 100; const polling = 100;
const watchdog = page const watchdog = page
.waitForFunction(async () => globalThis.__FOO === 'hit', { polling }) .waitForFunction(
.then(() => (success = true)); async () => {
await page.evaluate(async () => (globalThis.__FOO = 'hit')); return (globalThis as any).__FOO === 'hit';
},
{
polling,
}
)
.then(() => {
return (success = true);
});
await page.evaluate(async () => {
return ((globalThis as any).__FOO = 'hit');
});
expect(success).toBe(false); expect(success).toBe(false);
await page.evaluate(async () => await page.evaluate(async () => {
document.body.appendChild(document.createElement('div')) return document.body.appendChild(document.createElement('div'));
); });
await watchdog; await watchdog;
expect(Date.now() - startTime).not.toBeLessThan(polling / 2); expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
}); });
@ -190,15 +228,24 @@ describe('waittask specs', function () {
let success = false; let success = false;
const watchdog = page const watchdog = page
.waitForFunction(() => globalThis.__FOO === 'hit', { .waitForFunction(
() => {
return (globalThis as any).__FOO === 'hit';
},
{
polling: 'mutation', polling: 'mutation',
}) }
.then(() => (success = true)); )
await page.evaluate(() => (globalThis.__FOO = 'hit')); .then(() => {
return (success = true);
});
await page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
});
expect(success).toBe(false); expect(success).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.body.appendChild(document.createElement('div')) return document.body.appendChild(document.createElement('div'));
); });
await watchdog; await watchdog;
}); });
it('should poll on mutation async', async () => { it('should poll on mutation async', async () => {
@ -206,36 +253,56 @@ describe('waittask specs', function () {
let success = false; let success = false;
const watchdog = page const watchdog = page
.waitForFunction(async () => globalThis.__FOO === 'hit', { .waitForFunction(
async () => {
return (globalThis as any).__FOO === 'hit';
},
{
polling: 'mutation', polling: 'mutation',
}) }
.then(() => (success = true)); )
await page.evaluate(async () => (globalThis.__FOO = 'hit')); .then(() => {
return (success = true);
});
await page.evaluate(async () => {
return ((globalThis as any).__FOO = 'hit');
});
expect(success).toBe(false); expect(success).toBe(false);
await page.evaluate(async () => await page.evaluate(async () => {
document.body.appendChild(document.createElement('div')) return document.body.appendChild(document.createElement('div'));
); });
await watchdog; await watchdog;
}); });
it('should poll on raf', async () => { it('should poll on raf', async () => {
const { page } = getTestState(); const { page } = getTestState();
const watchdog = page.waitForFunction(() => globalThis.__FOO === 'hit', { const watchdog = page.waitForFunction(
() => {
return (globalThis as any).__FOO === 'hit';
},
{
polling: 'raf', polling: 'raf',
}
);
await page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
}); });
await page.evaluate(() => (globalThis.__FOO = 'hit'));
await watchdog; await watchdog;
}); });
it('should poll on raf async', async () => { it('should poll on raf async', async () => {
const { page } = getTestState(); const { page } = getTestState();
const watchdog = page.waitForFunction( const watchdog = page.waitForFunction(
async () => globalThis.__FOO === 'hit', async () => {
return (globalThis as any).__FOO === 'hit';
},
{ {
polling: 'raf', polling: 'raf',
} }
); );
await page.evaluate(async () => (globalThis.__FOO = 'hit')); await page.evaluate(async () => {
return ((globalThis as any).__FOO = 'hit');
});
await watchdog; await watchdog;
}); });
itFailsFirefox('should work with strict CSP policy', async () => { itFailsFirefox('should work with strict CSP policy', async () => {
@ -243,100 +310,148 @@ describe('waittask specs', function () {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX); server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let error = null; let error!: Error;
await Promise.all([ await Promise.all([
page page
.waitForFunction(() => globalThis.__FOO === 'hit', { polling: 'raf' }) .waitForFunction(
.catch((error_) => (error = error_)), () => {
page.evaluate(() => (globalThis.__FOO = 'hit')), return (globalThis as any).__FOO === 'hit';
},
{
polling: 'raf',
}
)
.catch((error_) => {
return (error = error_);
}),
page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
}),
]); ]);
expect(error).toBe(null); expect(error).toBeUndefined();
}); });
it('should throw on bad polling value', async () => { it('should throw on bad polling value', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
try { try {
await page.waitForFunction(() => !!document.body, { await page.waitForFunction(
() => {
return !!document.body;
},
{
polling: 'unknown', polling: 'unknown',
});
} catch (error_) {
error = error_;
} }
expect(error).toBeTruthy(); );
expect(error.message).toContain('polling'); } catch (error_) {
if (isErrorLike(error_)) {
error = error_ as Error;
}
}
expect(error?.message).toContain('polling');
}); });
it('should throw negative polling interval', async () => { it('should throw negative polling interval', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error = null; let error!: Error;
try { try {
await page.waitForFunction(() => !!document.body, { polling: -10 }); await page.waitForFunction(
() => {
return !!document.body;
},
{ polling: -10 }
);
} catch (error_) { } catch (error_) {
error = error_; if (isErrorLike(error_)) {
error = error_ as Error;
} }
expect(error).toBeTruthy(); }
expect(error.message).toContain('Cannot poll with non-positive interval'); expect(error?.message).toContain(
'Cannot poll with non-positive interval'
);
}); });
it('should return the success value as a JSHandle', async () => { it('should return the success value as a JSHandle', async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5); expect(
await (
await page.waitForFunction(() => {
return 5;
})
).jsonValue()
).toBe(5);
}); });
it('should return the window as a success value', async () => { it('should return the window as a success value', async () => {
const { page } = getTestState(); const { page } = getTestState();
expect(await page.waitForFunction(() => window)).toBeTruthy(); expect(
await page.waitForFunction(() => {
return window;
})
).toBeTruthy();
}); });
it('should accept ElementHandle arguments', async () => { it('should accept ElementHandle arguments', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent('<div></div>'); await page.setContent('<div></div>');
const div = await page.$('div'); const div = (await page.$('div'))!;
let resolved = false; let resolved = false;
const waitForFunction = page const waitForFunction = page
.waitForFunction( .waitForFunction(
(element) => element.localName === 'div' && !element.parentElement, (element: Element) => {
return element.localName === 'div' && !element.parentElement;
},
{}, {},
div div
) )
.then(() => (resolved = true)); .then(() => {
return (resolved = true);
});
expect(resolved).toBe(false); expect(resolved).toBe(false);
await page.evaluate((element: HTMLElement) => element.remove(), div); await page.evaluate((element: HTMLElement) => {
return element.remove();
}, div);
await waitForFunction; await waitForFunction;
}); });
it('should respect timeout', async () => { it('should respect timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
let error = null; let error!: Error;
await page await page.waitForFunction('false', { timeout: 10 }).catch((error_) => {
.waitForFunction('false', { timeout: 10 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for function failed: timeout');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error?.message).toContain('waiting for function failed: timeout');
}); });
it('should respect default timeout', async () => { it('should respect default timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
page.setDefaultTimeout(1); page.setDefaultTimeout(1);
let error = null; let error!: Error;
await page.waitForFunction('false').catch((error_) => (error = error_)); await page.waitForFunction('false').catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error.message).toContain('waiting for function failed: timeout'); expect(error?.message).toContain('waiting for function failed: timeout');
}); });
it('should disable timeout when its set to 0', async () => { it('should disable timeout when its set to 0', async () => {
const { page } = getTestState(); const { page } = getTestState();
const watchdog = page.waitForFunction( const watchdog = page.waitForFunction(
() => { () => {
globalThis.__counter = (globalThis.__counter || 0) + 1; (globalThis as any).__counter =
return globalThis.__injected; ((globalThis as any).__counter || 0) + 1;
return (globalThis as any).__injected;
}, },
{ timeout: 0, polling: 10 } { timeout: 0, polling: 10 }
); );
await page.waitForFunction(() => globalThis.__counter > 10); await page.waitForFunction(() => {
await page.evaluate(() => (globalThis.__injected = true)); return (globalThis as any).__counter > 10;
});
await page.evaluate(() => {
return ((globalThis as any).__injected = true);
});
await watchdog; await watchdog;
}); });
it('should survive cross-process navigation', async () => { it('should survive cross-process navigation', async () => {
@ -345,24 +460,32 @@ describe('waittask specs', function () {
let fooFound = false; let fooFound = false;
const waitForFunction = page const waitForFunction = page
.waitForFunction('globalThis.__FOO === 1') .waitForFunction('globalThis.__FOO === 1')
.then(() => (fooFound = true)); .then(() => {
return (fooFound = true);
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(fooFound).toBe(false); expect(fooFound).toBe(false);
await page.reload(); await page.reload();
expect(fooFound).toBe(false); expect(fooFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html'); await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
expect(fooFound).toBe(false); expect(fooFound).toBe(false);
await page.evaluate(() => (globalThis.__FOO = 1)); await page.evaluate(() => {
return ((globalThis as any).__FOO = 1);
});
await waitForFunction; await waitForFunction;
expect(fooFound).toBe(true); expect(fooFound).toBe(true);
}); });
it('should survive navigations', async () => { it('should survive navigations', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
const watchdog = page.waitForFunction(() => globalThis.__done); const watchdog = page.waitForFunction(() => {
return (globalThis as any).__done;
});
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.goto(server.PREFIX + '/consolelog.html'); await page.goto(server.PREFIX + '/consolelog.html');
await page.evaluate(() => (globalThis.__done = true)); await page.evaluate(() => {
return ((globalThis as any).__done = true);
});
await watchdog; await watchdog;
}); });
}); });
@ -401,8 +524,9 @@ describe('waittask specs', function () {
}); });
describe('Frame.waitForSelector', function () { describe('Frame.waitForSelector', function () {
const addElement = (tag) => const addElement = (tag: string) => {
document.body.appendChild(document.createElement(tag)); return document.body.appendChild(document.createElement(tag));
};
it('should immediately resolve promise if node exists', async () => { it('should immediately resolve promise if node exists', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
@ -417,13 +541,18 @@ describe('waittask specs', function () {
itFailsFirefox('should work with removed MutationObserver', async () => { itFailsFirefox('should work with removed MutationObserver', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => {
// @ts-expect-error We want to remove it for the test.
return delete window.MutationObserver;
});
const [handle] = await Promise.all([ const [handle] = await Promise.all([
page.waitForSelector('.zombo'), page.waitForSelector('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`), page.setContent(`<div class='zombo'>anything</div>`),
]); ]);
expect( expect(
await page.evaluate((x: HTMLElement) => x.textContent, handle) await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, handle)
).toBe('anything'); ).toBe('anything');
}); });
@ -435,10 +564,8 @@ describe('waittask specs', function () {
const watchdog = frame.waitForSelector('div'); const watchdog = frame.waitForSelector('div');
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = (await watchdog)!;
const tagName = await eHandle const tagName = await (await eHandle.getProperty('tagName')).jsonValue();
.getProperty('tagName')
.then((e) => e.jsonValue());
expect(tagName).toBe('DIV'); expect(tagName).toBe('DIV');
}); });
@ -448,10 +575,10 @@ describe('waittask specs', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div'); const watchdog = page.waitForSelector('h3 div');
await page.evaluate(addElement, 'span'); await page.evaluate(addElement, 'span');
await page.evaluate( await page.evaluate(() => {
() => return (document.querySelector('span')!.innerHTML =
(document.querySelector('span').innerHTML = '<h3><div></div></h3>') '<h3><div></div></h3>');
); });
await watchdog; await watchdog;
}); });
@ -461,43 +588,43 @@ describe('waittask specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1]; const otherFrame = page.frames()[1]!;
const watchdog = page.waitForSelector('div'); const watchdog = page.waitForSelector('div');
await otherFrame.evaluate(addElement, 'div'); await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
expect(eHandle.executionContext().frame()).toBe(page.mainFrame()); expect(eHandle?.executionContext().frame()).toBe(page.mainFrame());
} }
); );
itFailsFirefox('should run in specified frame', async () => { itFailsFirefox('should run in specified frame', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1]!;
const frame2 = page.frames()[2]; const frame2 = page.frames()[2]!;
const waitForSelectorPromise = frame2.waitForSelector('div'); const waitForSelectorPromise = frame2.waitForSelector('div');
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise; const eHandle = await waitForSelectorPromise;
expect(eHandle.executionContext().frame()).toBe(frame2); expect(eHandle?.executionContext().frame()).toBe(frame2);
}); });
itFailsFirefox('should throw when frame is detached', async () => { itFailsFirefox('should throw when frame is detached', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1]!;
let waitError = null; let waitError: Error | undefined;
const waitPromise = frame const waitPromise = frame.waitForSelector('.box').catch((error) => {
.waitForSelector('.box') return (waitError = error);
.catch((error) => (waitError = error)); });
await utils.detachFrame(page, 'frame1'); await detachFrame(page, 'frame1');
await waitPromise; await waitPromise;
expect(waitError).toBeTruthy(); expect(waitError).toBeTruthy();
expect(waitError.message).toContain( expect(waitError?.message).toContain(
'waitForFunction failed: frame got detached.' 'waitForFunction failed: frame got detached.'
); );
}); });
@ -505,9 +632,9 @@ describe('waittask specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
let boxFound = false; let boxFound = false;
const waitForSelector = page const waitForSelector = page.waitForSelector('.box').then(() => {
.waitForSelector('.box') return (boxFound = true);
.then(() => (boxFound = true)); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(boxFound).toBe(false); expect(boxFound).toBe(false);
await page.reload(); await page.reload();
@ -522,18 +649,22 @@ describe('waittask specs', function () {
let divFound = false; let divFound = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('div', { visible: true }) .waitForSelector('div', { visible: true })
.then(() => (divFound = true)); .then(() => {
return (divFound = true);
});
await page.setContent( await page.setContent(
`<div style='display: none; visibility: hidden;'>1</div>` `<div style='display: none; visibility: hidden;'>1</div>`
); );
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('display') return document.querySelector('div')?.style.removeProperty('display');
); });
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('visibility') return document
); .querySelector('div')
?.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divFound).toBe(true); expect(divFound).toBe(true);
}); });
@ -543,18 +674,22 @@ describe('waittask specs', function () {
let divVisible = false; let divVisible = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('div#inner', { visible: true }) .waitForSelector('div#inner', { visible: true })
.then(() => (divVisible = true)); .then(() => {
return (divVisible = true);
});
await page.setContent( await page.setContent(
`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>` `<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`
); );
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('display') return document.querySelector('div')?.style.removeProperty('display');
); });
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.removeProperty('visibility') return document
); .querySelector('div')
?.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true); expect(divVisible).toBe(true);
}); });
@ -565,12 +700,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`); await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page const waitForSelector = page
.waitForSelector('div', { hidden: true }) .waitForSelector('div', { hidden: true })
.then(() => (divHidden = true)); .then(() => {
return (divHidden = true);
});
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.setProperty('visibility', 'hidden') return document
); .querySelector('div')
?.style.setProperty('visibility', 'hidden');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
@ -581,12 +720,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`); await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page const waitForSelector = page
.waitForSelector('div', { hidden: true }) .waitForSelector('div', { hidden: true })
.then(() => (divHidden = true)); .then(() => {
return (divHidden = true);
});
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.setProperty('display', 'none') return document
); .querySelector('div')
?.style.setProperty('display', 'none');
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
@ -597,10 +740,14 @@ describe('waittask specs', function () {
let divRemoved = false; let divRemoved = false;
const waitForSelector = page const waitForSelector = page
.waitForSelector('div', { hidden: true }) .waitForSelector('div', { hidden: true })
.then(() => (divRemoved = true)); .then(() => {
return (divRemoved = true);
});
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false); expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove()); await page.evaluate(() => {
return document.querySelector('div')?.remove();
});
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
expect(divRemoved).toBe(true); expect(divRemoved).toBe(true);
}); });
@ -615,26 +762,27 @@ describe('waittask specs', function () {
it('should respect timeout', async () => { it('should respect timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
let error = null; let error!: Error;
await page await page.waitForSelector('div', { timeout: 10 }).catch((error_) => {
.waitForSelector('div', { timeout: 10 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeTruthy(); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error.message).toContain( expect(error?.message).toContain(
'waiting for selector `div` failed: timeout' 'waiting for selector `div` failed: timeout'
); );
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
it('should have an error message specifically for awaiting an element to be hidden', async () => { it('should have an error message specifically for awaiting an element to be hidden', async () => {
const { page } = getTestState(); const { page } = getTestState();
await page.setContent(`<div></div>`); await page.setContent(`<div></div>`);
let error = null; let error!: Error;
await page await page
.waitForSelector('div', { hidden: true, timeout: 10 }) .waitForSelector('div', { hidden: true, timeout: 10 })
.catch((error_) => (error = error_)); .catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain( expect(error?.message).toContain(
'waiting for selector `div` to be hidden failed: timeout' 'waiting for selector `div` to be hidden failed: timeout'
); );
}); });
@ -643,14 +791,14 @@ describe('waittask specs', function () {
const { page } = getTestState(); const { page } = getTestState();
let divFound = false; let divFound = false;
const waitForSelector = page const waitForSelector = page.waitForSelector('.zombo').then(() => {
.waitForSelector('.zombo') return (divFound = true);
.then(() => (divFound = true)); });
await page.setContent(`<div class='notZombo'></div>`); await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate( await page.evaluate(() => {
() => (document.querySelector('div').className = 'zombo') return (document.querySelector('div')!.className = 'zombo');
); });
expect(await waitForSelector).toBe(true); expect(await waitForSelector).toBe(true);
}); });
it('should return the element handle', async () => { it('should return the element handle', async () => {
@ -659,28 +807,28 @@ describe('waittask specs', function () {
const waitForSelector = page.waitForSelector('.zombo'); const waitForSelector = page.waitForSelector('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect( expect(
await page.evaluate( await page.evaluate((x: HTMLElement) => {
(x: HTMLElement) => x.textContent, return x.textContent;
await waitForSelector }, await waitForSelector)
)
).toBe('anything'); ).toBe('anything');
}); });
it('should have correct stack trace for timeout', async () => { it('should have correct stack trace for timeout', async () => {
const { page } = getTestState(); const { page } = getTestState();
let error; let error!: Error;
await page await page.waitForSelector('.zombo', { timeout: 10 }).catch((error_) => {
.waitForSelector('.zombo', { timeout: 10 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error.stack).toContain('waiting for selector `.zombo` failed'); expect(error?.stack).toContain('waiting for selector `.zombo` failed');
// The extension is ts here as Mocha maps back via sourcemaps. // The extension is ts here as Mocha maps back via sourcemaps.
expect(error.stack).toContain('waittask.spec.ts'); expect(error?.stack).toContain('waittask.spec.ts');
}); });
}); });
describe('Frame.waitForXPath', function () { describe('Frame.waitForXPath', function () {
const addElement = (tag) => const addElement = (tag: string) => {
document.body.appendChild(document.createElement(tag)); return document.body.appendChild(document.createElement(tag));
};
it('should support some fancy xpath', async () => { it('should support some fancy xpath', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -690,51 +838,51 @@ describe('waittask specs', function () {
'//p[normalize-space(.)="hello world"]' '//p[normalize-space(.)="hello world"]'
); );
expect( expect(
await page.evaluate( await page.evaluate((x: HTMLElement) => {
(x: HTMLElement) => x.textContent, return x.textContent;
await waitForXPath }, await waitForXPath)
)
).toBe('hello world '); ).toBe('hello world ');
}); });
it('should respect timeout', async () => { it('should respect timeout', async () => {
const { page, puppeteer } = getTestState(); const { page, puppeteer } = getTestState();
let error = null; let error!: Error;
await page await page.waitForXPath('//div', { timeout: 10 }).catch((error_) => {
.waitForXPath('//div', { timeout: 10 }) return (error = error_);
.catch((error_) => (error = error_)); });
expect(error).toBeTruthy(); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error.message).toContain( expect(error?.message).toContain(
'waiting for XPath `//div` failed: timeout' 'waiting for XPath `//div` failed: timeout'
); );
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
}); });
itFailsFirefox('should run in specified frame', async () => { itFailsFirefox('should run in specified frame', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1]!;
const frame2 = page.frames()[2]; const frame2 = page.frames()[2]!;
const waitForXPathPromise = frame2.waitForXPath('//div'); const waitForXPathPromise = frame2.waitForXPath('//div');
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise; const eHandle = await waitForXPathPromise;
expect(eHandle.executionContext().frame()).toBe(frame2); expect(eHandle?.executionContext().frame()).toBe(frame2);
}); });
itFailsFirefox('should throw when frame is detached', async () => { itFailsFirefox('should throw when frame is detached', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1]!;
let waitError = null; let waitError: Error | undefined;
const waitPromise = frame const waitPromise = frame
.waitForXPath('//*[@class="box"]') .waitForXPath('//*[@class="box"]')
.catch((error) => (waitError = error)); .catch((error) => {
await utils.detachFrame(page, 'frame1'); return (waitError = error);
});
await detachFrame(page, 'frame1');
await waitPromise; await waitPromise;
expect(waitError).toBeTruthy(); expect(waitError).toBeTruthy();
expect(waitError.message).toContain( expect(waitError?.message).toContain(
'waitForFunction failed: frame got detached.' 'waitForFunction failed: frame got detached.'
); );
}); });
@ -745,12 +893,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`); await page.setContent(`<div style='display: block;'></div>`);
const waitForXPath = page const waitForXPath = page
.waitForXPath('//div', { hidden: true }) .waitForXPath('//div', { hidden: true })
.then(() => (divHidden = true)); .then(() => {
return (divHidden = true);
});
await page.waitForXPath('//div'); // do a round trip await page.waitForXPath('//div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => await page.evaluate(() => {
document.querySelector('div').style.setProperty('display', 'none') return document
); .querySelector('div')
?.style.setProperty('display', 'none');
});
expect(await waitForXPath).toBe(true); expect(await waitForXPath).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
@ -760,10 +912,9 @@ describe('waittask specs', function () {
const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect( expect(
await page.evaluate( await page.evaluate((x: HTMLElement) => {
(x: HTMLElement) => x.textContent, return x.textContent;
await waitForXPath }, await waitForXPath)
)
).toBe('anything'); ).toBe('anything');
}); });
it('should allow you to select a text node', async () => { it('should allow you to select a text node', async () => {
@ -771,7 +922,7 @@ describe('waittask specs', function () {
await page.setContent(`<div>some text</div>`); await page.setContent(`<div>some text</div>`);
const text = await page.waitForXPath('//div/text()'); const text = await page.waitForXPath('//div/text()');
expect(await (await text.getProperty('nodeType')).jsonValue()).toBe( expect(await (await text!.getProperty('nodeType')!).jsonValue()).toBe(
3 /* Node.TEXT_NODE */ 3 /* Node.TEXT_NODE */
); );
}); });
@ -781,10 +932,9 @@ describe('waittask specs', function () {
await page.setContent(`<div>some text</div>`); await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForXPath('/html/body/div'); const waitForXPath = page.waitForXPath('/html/body/div');
expect( expect(
await page.evaluate( await page.evaluate((x: HTMLElement) => {
(x: HTMLElement) => x.textContent, return x.textContent;
await waitForXPath }, await waitForXPath)
)
).toBe('some text'); ).toBe('some text');
}); });
}); });

View File

@ -15,16 +15,15 @@
*/ */
import expect from 'expect'; import expect from 'expect';
import { ConsoleMessage } from '../../lib/cjs/puppeteer/common/ConsoleMessage.js';
import { WebWorker } from '../../lib/cjs/puppeteer/common/WebWorker.js';
import { import {
describeFailsFirefox,
getTestState, getTestState,
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
describeFailsFirefox, } from './mocha-utils.js';
} from './mocha-utils'; // eslint-disable-line import/extensions import { waitEvent } from './utils.js';
import utils from './utils.js';
import { WebWorker } from '../../lib/cjs/puppeteer/common/WebWorker.js';
import { ConsoleMessage } from '../../lib/cjs/puppeteer/common/ConsoleMessage.js';
const { waitEvent } = utils;
describeFailsFirefox('Workers', function () { describeFailsFirefox('Workers', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -33,15 +32,19 @@ describeFailsFirefox('Workers', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await Promise.all([ await Promise.all([
new Promise((x) => page.once('workercreated', x)), new Promise((x) => {
return page.once('workercreated', x);
}),
page.goto(server.PREFIX + '/worker/worker.html'), page.goto(server.PREFIX + '/worker/worker.html'),
]); ]);
const worker = page.workers()[0]; const worker = page.workers()[0]!;
expect(worker.url()).toContain('worker.js'); expect(worker?.url()).toContain('worker.js');
expect(await worker.evaluate(() => globalThis.workerFunction())).toBe( expect(
'worker function result' await worker?.evaluate(() => {
); return (globalThis as any).workerFunction();
})
).toBe('worker function result');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(page.workers().length).toBe(0); expect(page.workers().length).toBe(0);
@ -49,25 +52,26 @@ describeFailsFirefox('Workers', function () {
it('should emit created and destroyed events', async () => { it('should emit created and destroyed events', async () => {
const { page } = getTestState(); const { page } = getTestState();
const workerCreatedPromise = new Promise<WebWorker>((x) => const workerCreatedPromise = new Promise<WebWorker>((x) => {
page.once('workercreated', x) return page.once('workercreated', x);
); });
const workerObj = await page.evaluateHandle( const workerObj = await page.evaluateHandle(() => {
() => new Worker('data:text/javascript,1') return new Worker('data:text/javascript,1');
); });
const worker = await workerCreatedPromise; const worker = await workerCreatedPromise;
const workerThisObj = await worker.evaluateHandle(() => this); const workerThisObj = await worker.evaluateHandle(() => {
const workerDestroyedPromise = new Promise((x) => return this;
page.once('workerdestroyed', x) });
); const workerDestroyedPromise = new Promise((x) => {
await page.evaluate( return page.once('workerdestroyed', x);
(workerObj: Worker) => workerObj.terminate(), });
workerObj await page.evaluate((workerObj: Worker) => {
); return workerObj.terminate();
}, workerObj);
expect(await workerDestroyedPromise).toBe(worker); expect(await workerDestroyedPromise).toBe(worker);
const error = await workerThisObj const error = await workerThisObj.getProperty('self').catch((error) => {
.getProperty('self') return error;
.catch((error) => error); });
expect(error.message).toContain('Most likely the worker has been closed.'); expect(error.message).toContain('Most likely the worker has been closed.');
}); });
it('should report console logs', async () => { it('should report console logs', async () => {
@ -75,7 +79,9 @@ describeFailsFirefox('Workers', function () {
const [message] = await Promise.all([ const [message] = await Promise.all([
waitEvent(page, 'console'), waitEvent(page, 'console'),
page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`)), page.evaluate(() => {
return new Worker(`data:text/javascript,console.log(1)`);
}),
]); ]);
expect(message.text()).toBe('1'); expect(message.text()).toBe('1');
expect(message.location()).toEqual({ expect(message.location()).toEqual({
@ -87,39 +93,42 @@ describeFailsFirefox('Workers', function () {
it('should have JSHandles for console logs', async () => { it('should have JSHandles for console logs', async () => {
const { page } = getTestState(); const { page } = getTestState();
const logPromise = new Promise<ConsoleMessage>((x) => const logPromise = new Promise<ConsoleMessage>((x) => {
page.on('console', x) return page.on('console', x);
); });
await page.evaluate( await page.evaluate(() => {
() => new Worker(`data:text/javascript,console.log(1,2,3,this)`) return new Worker(`data:text/javascript,console.log(1,2,3,this)`);
); });
const log = await logPromise; const log = await logPromise;
expect(log.text()).toBe('1 2 3 JSHandle@object'); expect(log.text()).toBe('1 2 3 JSHandle@object');
expect(log.args().length).toBe(4); expect(log.args().length).toBe(4);
expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe( expect(await (await log.args()[3]!.getProperty('origin')).jsonValue()).toBe(
'null' 'null'
); );
}); });
it('should have an execution context', async () => { it('should have an execution context', async () => {
const { page } = getTestState(); const { page } = getTestState();
const workerCreatedPromise = new Promise<WebWorker>((x) => const workerCreatedPromise = new Promise<WebWorker>((x) => {
page.once('workercreated', x) return page.once('workercreated', x);
); });
await page.evaluate( await page.evaluate(() => {
() => new Worker(`data:text/javascript,console.log(1)`) return new Worker(`data:text/javascript,console.log(1)`);
); });
const worker = await workerCreatedPromise; const worker = await workerCreatedPromise;
expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2); expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2);
}); });
it('should report errors', async () => { it('should report errors', async () => {
const { page } = getTestState(); const { page } = getTestState();
const errorPromise = new Promise<Error>((x) => page.on('pageerror', x)); const errorPromise = new Promise<Error>((x) => {
await page.evaluate( return page.on('pageerror', x);
() => });
new Worker(`data:text/javascript, throw new Error('this is my error');`) await page.evaluate(() => {
return new Worker(
`data:text/javascript, throw new Error('this is my error');`
); );
});
const errorLog = await errorPromise; const errorLog = await errorPromise;
expect(errorLog.message).toContain('this is my error'); expect(errorLog.message).toContain('this is my error');
}); });

View File

@ -1,17 +1,11 @@
{ {
"extends": "../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true,
"allowJs": true,
"checkJs": false,
"target": "esnext",
"module": "CommonJS",
"moduleResolution": "node",
"declaration": false, "declaration": false,
"declarationMap": false, "declarationMap": false,
"resolveJsonModule": true, "module": "CommonJS",
"sourceMap": true, "outDir": "build",
"rootDir": "src", "rootDir": "src"
"outDir": "build"
}, },
"include": ["src"], "include": ["src"],
"references": [ "references": [

View File

@ -43,10 +43,10 @@ interface Subscriber {
type TestIncomingMessage = IncomingMessage & { postBody?: Promise<string> }; type TestIncomingMessage = IncomingMessage & { postBody?: Promise<string> };
export class TestServer { export class TestServer {
PORT?: number; PORT!: number;
PREFIX?: string; PREFIX!: string;
CROSS_PROCESS_PREFIX?: string; CROSS_PROCESS_PREFIX!: string;
EMPTY_PAGE?: string; EMPTY_PAGE!: string;
#dirPath: string; #dirPath: string;
#server: HttpsServer | HttpServer; #server: HttpsServer | HttpServer;

View File

@ -1,11 +1,11 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"composite": true,
"allowJs": true, "allowJs": true,
"module": "CommonJS" "composite": true,
"module": "CommonJS",
"outDir": "lib",
"rootDir": "src"
}, },
"include": ["src"] "include": ["src"]
} }