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*
/.local-chromium/
/.local-firefox/
/test/test-user-data-dir*
/test/output-*/
build/
coverage
coverage/

View File

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

View File

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

View File

@ -719,10 +719,10 @@ export class DOMWorld {
function deliverResult(name: string, seq: number, result: unknown): void {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @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
// @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 => {
const debugLevel = globalThis.__PUPPETEER_DEBUG;
const debugLevel = (globalThis as any).__PUPPETEER_DEBUG;
if (!debugLevel) {
return;
}

View File

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

View File

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

View File

@ -2293,7 +2293,9 @@ export class Page extends EventEmitter {
*/
async emulateMediaType(type?: string): Promise<void> {
assert(
type === 'screen' || type === 'print' || type === null,
type === 'screen' ||
type === 'print' ||
(type ?? undefined) === undefined,
'Unsupported media type: ' + type
);
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. */
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 = {
rules: {
'arrow-body-style': ['error', 'always'],
'no-restricted-imports': [
'error',
{

View File

@ -2,7 +2,9 @@
const [, , puppeteerRoot, options] = process.argv;
const browser = await require(puppeteerRoot).launch(JSON.parse(options));
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 browser.close();
})();

View File

@ -21,7 +21,8 @@ import {
setupTestBrowserHooks,
setupTestPageAndContextHooks,
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 () {
setupTestBrowserHooks();
@ -36,7 +37,9 @@ describeChromeOnly('Target.createCDPSession', function () {
client.send('Runtime.enable'),
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');
});
it('should send events', async () => {
@ -45,7 +48,9 @@ describeChromeOnly('Target.createCDPSession', function () {
const client = await page.target().createCDPSession();
await client.send('Network.enable');
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);
expect(events.length).toBe(1);
});
@ -77,14 +82,16 @@ describeChromeOnly('Target.createCDPSession', function () {
});
expect(evalResponse.result.value).toBe(3);
await client.detach();
let error = null;
let error!: Error;
try {
await client.send('Runtime.evaluate', {
expression: '3 + 1',
returnByValue: true,
});
} catch (error_) {
error = error_;
if (isErrorLike(error_)) {
error = error_ as Error;
}
}
expect(error.message).toContain('Session closed.');
});
@ -92,7 +99,9 @@ describeChromeOnly('Target.createCDPSession', function () {
const { page } = getTestState();
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.message).toContain('ThisCommand.DoesNotExist');

View File

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

View File

@ -14,7 +14,7 @@
* 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 {

View File

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

View File

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

View File

@ -15,7 +15,7 @@
*/
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 () {
setupTestBrowserHooks();
@ -60,7 +60,7 @@ describe('Browser specs', function () {
const { browser } = getTestState();
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 () => {
const { browser, puppeteer } = getTestState();

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import {
setupTestPageAndContextHooks,
setupTestBrowserHooks,
itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
import utils from './utils.js';
describe('Page.click', function () {
@ -31,7 +31,11 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/button.html');
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 () => {
const { page } = getTestState();
@ -42,7 +46,11 @@ describe('Page.click', function () {
</svg>
`);
await page.click('circle');
expect(await page.evaluate(() => globalThis.__CLICKED)).toBe(42);
expect(
await page.evaluate(() => {
return (globalThis as any).__CLICKED;
})
).toBe(42);
});
itFailsFirefox(
'should click the button if window.Node is removed',
@ -50,9 +58,16 @@ describe('Page.click', function () {
const { page, server } = getTestState();
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');
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
@ -68,7 +83,11 @@ describe('Page.click', function () {
<span onclick='javascript:window.CLICKED=42'></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 () => {
const { page } = getTestState();
@ -85,7 +104,11 @@ describe('Page.click', function () {
await page.click('button');
await page.goto(server.PREFIX + '/input/button.html');
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 () => {
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>
`);
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 () => {
const { page, server } = getTestState();
@ -124,9 +151,9 @@ describe('Page.click', function () {
expect(
await page.evaluate(() => {
const textarea = document.querySelector('textarea');
return textarea.value.substring(
textarea.selectionStart,
textarea.selectionEnd
return textarea!.value.substring(
textarea!.selectionStart,
textarea!.selectionEnd
);
})
).toBe(text);
@ -135,11 +162,15 @@ describe('Page.click', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/offscreenbuttons.html');
const messages = [];
page.on('console', (msg) => messages.push(msg.text()));
const messages: any[] = [];
page.on('console', (msg) => {
return messages.push(msg.text());
});
for (let i = 0; i < 11; ++i) {
// 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}`);
}
expect(messages).toEqual([
@ -162,17 +193,33 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/wrappedlink.html');
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 () => {
const { page, server } = getTestState();
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');
expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
expect(await page.evaluate(() => globalThis.result.events)).toEqual([
expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).result.events;
})
).toEqual([
'mouseover',
'mouseenter',
'mousemove',
@ -183,33 +230,49 @@ describe('Page.click', function () {
'change',
]);
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 () => {
const { page, server } = getTestState();
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"]');
expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
expect(await page.evaluate(() => globalThis.result.events)).toEqual([
'click',
'input',
'change',
]);
expect(
await page.evaluate(() => {
return (globalThis as any).result.check;
})
).toBe(true);
expect(
await page.evaluate(() => {
return (globalThis as any).result.events;
})
).toEqual(['click', 'input', 'change']);
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 () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
let error = null;
await page
.click('button.does-not-exist')
.catch((error_) => (error = error_));
let error!: Error;
await page.click('button.does-not-exist').catch((error_) => {
return (error = error_);
});
expect(error.message).toBe(
'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 () => {
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.move(100, 10);
await page.mouse.up();
@ -229,13 +292,15 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-5');
expect(
await page.evaluate(() => document.querySelector('#button-5').textContent)
await page.evaluate(() => {
return document.querySelector('#button-5')!.textContent;
})
).toBe('clicked');
await page.click('#button-80');
expect(
await page.evaluate(
() => document.querySelector('#button-80').textContent
)
await page.evaluate(() => {
return document.querySelector('#button-80')!.textContent;
})
).toBe('clicked');
});
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.evaluate(() => {
globalThis.double = false;
(globalThis as any).double = false;
const button = document.querySelector('button');
button.addEventListener('dblclick', () => {
globalThis.double = true;
button!.addEventListener('dblclick', () => {
(globalThis as any).double = true;
});
});
const button = await page.$('button');
await button.click({ clickCount: 2 });
const button = (await page.$('button'))!;
await button!.click({ clickCount: 2 });
expect(await page.evaluate('double')).toBe(true);
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.evaluate(() => {
const button = document.querySelector('button');
button.textContent = 'Some really long text that will go offscreen';
button.style.position = 'absolute';
button.style.left = '368px';
button!.textContent = 'Some really long text that will go offscreen';
button!.style.position = 'absolute';
button!.style.left = '368px';
});
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 () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/rotatedButton.html');
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 () => {
const { page, server } = getTestState();
@ -280,7 +353,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'right' });
expect(
await page.evaluate(() => document.querySelector('#button-8').textContent)
await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('context menu');
});
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.click('#button-8', { button: 'middle' });
expect(
await page.evaluate(() => document.querySelector('#button-8').textContent)
await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('aux click');
});
it('should fire back click', async () => {
@ -298,7 +375,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'back' });
expect(
await page.evaluate(() => document.querySelector('#button-8').textContent)
await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('back click');
});
it('should fire forward click', async () => {
@ -307,7 +386,9 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.click('#button-8', { button: 'forward' });
expect(
await page.evaluate(() => document.querySelector('#button-8').textContent)
await page.evaluate(() => {
return document.querySelector('#button-8')!.textContent;
})
).toBe('forward click');
});
// @see https://github.com/puppeteer/puppeteer/issues/206
@ -329,9 +410,13 @@ describe('Page.click', function () {
server.PREFIX + '/input/button.html'
);
const frame = page.frames()[1];
const button = await frame.$('button');
await button.click();
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
const button = await frame!.$('button');
await button!.click();
expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
});
// @see https://github.com/puppeteer/puppeteer/issues/4110
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'
);
const frame = page.frames()[1];
await frame.$eval('button', (button: HTMLElement) =>
button.style.setProperty('position', 'fixed')
);
await frame.click('button');
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
await frame!.$eval('button', (button: Element) => {
return (button as HTMLElement).style.setProperty('position', 'fixed');
});
await frame!.click('button');
expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
});
it('should click the button with deviceScaleFactor set', async () => {
const { page, server } = getTestState();
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 utils.attachFrame(
page,
@ -366,8 +459,12 @@ describe('Page.click', function () {
server.PREFIX + '/input/button.html'
);
const frame = page.frames()[1];
const button = await frame.$('button');
await button.click();
expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
const button = await frame!.$('button');
await button!.click();
expect(
await frame!.evaluate(() => {
return (globalThis as any).result;
})
).toBe('Clicked');
});
});

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks,
setupTestPageAndContextHooks,
itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
describe('Cookie specs', () => {
setupTestBrowserHooks();
@ -58,36 +58,36 @@ describe('Cookie specs', () => {
});
it('should properly report httpOnly cookie', async () => {
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.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies();
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 () => {
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.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies();
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 () => {
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.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await page.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Lax');
expect(cookies[0]!.sameSite).toBe('Lax');
});
it('should get multiple cookies', async () => {
const { page, server } = getTestState();
@ -97,7 +97,9 @@ describe('Cookie specs', () => {
document.cookie = 'password=1234';
});
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, [
{
name: 'password',
@ -149,7 +151,9 @@ describe('Cookie specs', () => {
}
);
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, [
{
name: 'birdo',
@ -191,9 +195,11 @@ describe('Cookie specs', () => {
name: 'password',
value: '123456',
});
expect(await page.evaluate(() => document.cookie)).toEqual(
'password=123456'
);
expect(
await page.evaluate(() => {
return document.cookie;
})
).toEqual('password=123456');
});
itFailsFirefox('should isolate cookies in browser contexts', async () => {
const { page, server, browser } = getTestState();
@ -211,10 +217,10 @@ describe('Cookie specs', () => {
const cookies2 = await anotherPage.cookies();
expect(cookies1.length).toBe(1);
expect(cookies2.length).toBe(1);
expect(cookies1[0].name).toBe('page1cookie');
expect(cookies1[0].value).toBe('page1value');
expect(cookies2[0].name).toBe('page2cookie');
expect(cookies2[0].value).toBe('page2value');
expect(cookies1[0]!.name).toBe('page1cookie');
expect(cookies1[0]!.value).toBe('page1value');
expect(cookies2[0]!.name).toBe('page2cookie');
expect(cookies2[0]!.value).toBe('page2value');
await anotherContext.close();
});
itFailsFirefox('should set multiple cookies', async () => {
@ -233,7 +239,11 @@ describe('Cookie specs', () => {
);
const cookieStrings = await page.evaluate(() => {
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']);
@ -247,8 +257,8 @@ describe('Cookie specs', () => {
value: '123456',
});
const cookies = await page.cookies();
expect(cookies[0].session).toBe(true);
expect(cookies[0].expires).toBe(-1);
expect(cookies[0]!.session).toBe(true);
expect(cookies[0]!.expires).toBe(-1);
});
itFailsFirefox('should set cookie with reasonable defaults', async () => {
const { page, server } = getTestState();
@ -260,7 +270,9 @@ describe('Cookie specs', () => {
});
const cookies = await page.cookies();
expectCookieEquals(
cookies.sort((a, b) => a.name.localeCompare(b.name)),
cookies.sort((a, b) => {
return a.name.localeCompare(b.name);
}),
[
{
name: 'password',
@ -315,11 +327,11 @@ describe('Cookie specs', () => {
const { page } = getTestState();
await page.goto('about:blank');
let error = null;
let error!: Error;
try {
await page.setCookie({ name: 'example-cookie', value: 'best' });
} catch (error_) {
error = error_;
error = error_ as Error;
}
expect(error.message).toContain(
'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 () => {
const { page, server } = getTestState();
let error = null;
let error!: Error;
await page.goto(server.EMPTY_PAGE);
try {
await page.setCookie(
@ -336,7 +348,7 @@ describe('Cookie specs', () => {
{ url: 'about:blank', name: 'example-cookie-blank', value: 'best' }
);
} catch (error_) {
error = error_;
error = error_ as Error;
}
expect(error.message).toEqual(
`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 () => {
const { page } = getTestState();
let error = null;
let error!: Error;
await page.goto('data:,Hello%2C%20World!');
try {
await page.setCookie({ name: 'example-cookie', value: 'best' });
} catch (error_) {
error = error_;
error = error_ as Error;
}
expect(error.message).toContain(
'At least one of the url and domain needs to be specified'
@ -369,7 +381,7 @@ describe('Cookie specs', () => {
value: 'bar',
});
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 () => {
@ -383,7 +395,7 @@ describe('Cookie specs', () => {
value: 'bar',
});
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 () => {
const { page, server } = getTestState();
@ -419,9 +431,11 @@ describe('Cookie specs', () => {
await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({ name: 'localhost-cookie', value: 'best' });
await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill;
const promise = new Promise<void>((x) => (fulfill = x));
const iframe = document.createElement('iframe');
let fulfill!: () => void;
const promise = new Promise<void>((x) => {
return (fulfill = x);
});
const iframe = document.createElement('iframe') as HTMLIFrameElement;
document.body.appendChild(iframe);
iframe.onload = fulfill;
iframe.src = src;
@ -435,7 +449,7 @@ describe('Cookie specs', () => {
expect(await page.evaluate('document.cookie')).toBe(
'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(), [
{
@ -487,8 +501,10 @@ describe('Cookie specs', () => {
try {
await page.goto(httpsServer.PREFIX + '/grid.html');
await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill;
const promise = new Promise<void>((x) => (fulfill = x));
let fulfill!: () => void;
const promise = new Promise<void>((x) => {
return (fulfill = x);
});
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = fulfill;
@ -502,7 +518,7 @@ describe('Cookie specs', () => {
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'
);
expectCookieEquals(

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import {
setupTestPageAndContextHooks,
setupTestBrowserHooks,
describeChromeOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
describeChromeOnly('Input.drag', function () {
setupTestBrowserHooks();
@ -29,12 +29,14 @@ describeChromeOnly('Input.drag', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/drag-and-drop.html');
const draggable = await page.$('#drag');
const draggable = (await page.$('#drag'))!;
try {
await draggable.drag({ x: 1, y: 1 });
await draggable!.drag({ x: 1, y: 1 });
} 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 () => {
@ -44,11 +46,15 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(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 });
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 () => {
const { page, server } = getTestState();
@ -57,13 +63,21 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(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 dropzone = await page.$('#drop');
const dropzone = (await page.$('#drop'))!;
await dropzone.dragEnter(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
expect(
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 () => {
const { page, server } = getTestState();
@ -72,15 +86,27 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(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 dropzone = await page.$('#drop');
const dropzone = (await page.$('#drop'))!;
await dropzone.dragEnter(data);
await dropzone.dragOver(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
expect(
await page.evaluate(() => {
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 () => {
const { page, server } = getTestState();
@ -89,17 +115,33 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag');
const dropzone = await page.$('#drop');
const draggable = (await page.$('#drag'))!;
const dropzone = (await page.$('#drop'))!;
const data = await draggable.drag({ x: 1, y: 1 });
await dropzone.dragEnter(data);
await dropzone.dragOver(data);
await dropzone.drop(data);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
expect(await page.evaluate(() => globalThis.didDrop)).toBe(true);
expect(
await page.evaluate(() => {
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);
expect(
await page.evaluate(() => {
return (globalThis as any).didDrop;
})
).toBe(true);
});
it('can be dragged and dropped with a single function', async () => {
const { page, server } = getTestState();
@ -108,14 +150,30 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag');
const dropzone = await page.$('#drop');
const draggable = (await page.$('#drag'))!;
const dropzone = (await page.$('#drop'))!;
await draggable.dragAndDrop(dropzone);
expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
expect(await page.evaluate(() => globalThis.didDrop)).toBe(true);
expect(
await page.evaluate(() => {
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);
expect(
await page.evaluate(() => {
return (globalThis as any).didDrop;
})
).toBe(true);
});
it('can be disabled', async () => {
const { page, server } = getTestState();
@ -124,14 +182,16 @@ describeChromeOnly('Input.drag', function () {
expect(page.isDragInterceptionEnabled()).toBe(false);
await page.setDragInterception(true);
expect(page.isDragInterceptionEnabled()).toBe(true);
const draggable = await page.$('#drag');
const draggable = (await page.$('#drag'))!;
await draggable.drag({ x: 1, y: 1 });
await page.setDragInterception(false);
try {
await draggable.drag({ x: 1, y: 1 });
} 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,
describeFailsFirefox,
itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
import utils from './utils.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.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();
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.goto(server.PREFIX + '/frames/nested-frames.html');
const nestedFrame = page.frames()[1].childFrames()[1];
const elementHandle = await nestedFrame.$('div');
const nestedFrame = page.frames()[1]!.childFrames()[1]!;
const elementHandle = (await nestedFrame.$('div'))!;
const box = await elementHandle.boundingBox();
if (isChrome) {
expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 });
@ -59,7 +59,7 @@ describe('ElementHandle specs', function () {
const { page } = getTestState();
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);
});
it('should force a layout', async () => {
@ -69,11 +69,10 @@ describe('ElementHandle specs', function () {
await page.setContent(
'<div style="width: 100px; height: 100px">hello</div>'
);
const elementHandle = await page.$('div');
await page.evaluate<(element: HTMLElement) => void>(
(element) => (element.style.height = '200px'),
elementHandle
);
const elementHandle = (await page.$('div'))!;
await page.evaluate((element: HTMLElement) => {
return (element.style.height = '200px');
}, elementHandle);
const box = await elementHandle.boundingBox();
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>
</svg>
`);
const element = await page.$('#therect');
const element = (await page.$('#therect'))!;
const pptrBoundingBox = await element.boundingBox();
const webBoundingBox = await page.evaluate((e: HTMLElement) => {
const rect = e.getBoundingClientRect();
@ -104,14 +103,14 @@ describe('ElementHandle specs', function () {
// Step 1: Add Frame and position it absolutely.
await utils.attachFrame(page, 'frame1', server.PREFIX + '/resetcss.html');
await page.evaluate(() => {
const frame = document.querySelector<HTMLElement>('#frame1');
const frame = document.querySelector<HTMLElement>('#frame1')!;
frame.style.position = 'absolute';
frame.style.left = '1px';
frame.style.top = '2px';
});
// Step 2: Add div and position it absolutely inside frame.
const frame = page.frames()[1];
const frame = page.frames()[1]!;
const divHandle = (
await frame.evaluateHandle(() => {
const div = document.createElement('div');
@ -127,25 +126,25 @@ describe('ElementHandle specs', function () {
div.style.height = '7px';
return div;
})
).asElement();
).asElement()!;
// 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.height).toBe(7);
expect(box.margin[0]).toEqual({
expect(box.margin[0]!).toEqual({
x: 1 + 4, // frame.left + div.left
y: 2 + 5,
});
expect(box.border[0]).toEqual({
expect(box.border[0]!).toEqual({
x: 1 + 4 + 3, // frame.left + div.left + div.margin-left
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
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
y: 2 + 5,
});
@ -155,7 +154,7 @@ describe('ElementHandle specs', function () {
const { page } = getTestState();
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);
});
});
@ -166,9 +165,9 @@ describe('ElementHandle specs', function () {
await page.goto(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();
expect(frame).toBe(page.frames()[1]);
expect(frame).toBe(page.frames()[1]!);
});
});
@ -178,57 +177,68 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
const button = (await page.$('button'))!;
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 () => {
const { page, server } = getTestState();
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.
() => button
);
return button;
});
await buttonHandle.click();
expect(
await page.evaluate(
await page.evaluate(() => {
// @ts-expect-error clicked is expected to be in the page's scope.
() => clicked
)
return clicked;
})
).toBe(true);
});
it('should work for TextNodes', async () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle<ElementHandle>(
() => document.querySelector('button').firstChild
);
let error = null;
await buttonTextNode.click().catch((error_) => (error = error_));
const buttonTextNode = await page.evaluateHandle<ElementHandle>(() => {
return document.querySelector('button')!.firstChild;
});
let error!: Error;
await buttonTextNode.click().catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Node is not of type HTMLElement');
});
it('should throw for detached nodes', async () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate((button: HTMLElement) => button.remove(), button);
let error = null;
await button.click().catch((error_) => (error = error_));
const button = (await page.$('button'))!;
await page.evaluate((button: HTMLElement) => {
return button.remove();
}, button);
let error!: Error;
await button.click().catch((error_) => {
return (error = error_);
});
expect(error.message).toBe('Node is detached from document');
});
it('should throw for hidden nodes', async () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(
(button: HTMLElement) => (button.style.display = 'none'),
button
);
const error = await button.click().catch((error_) => error_);
const button = (await page.$('button'))!;
await page.evaluate((button: HTMLElement) => {
return (button.style.display = 'none');
}, button);
const error = await button.click().catch((error_) => {
return error_;
});
expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement'
);
@ -237,12 +247,13 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(
(button: HTMLElement) => (button.parentElement.style.display = 'none'),
button
);
const error = await button.click().catch((error_) => error_);
const button = (await page.$('button'))!;
await page.evaluate((button: HTMLElement) => {
return (button.parentElement!.style.display = 'none');
}, button);
const error = await button.click().catch((error_) => {
return error_;
});
expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement'
);
@ -251,8 +262,10 @@ describe('ElementHandle specs', function () {
const { page } = getTestState();
await page.setContent('hello<br>goodbye');
const br = await page.$('br');
const error = await br.click().catch((error_) => error_);
const br = (await page.$('br'))!;
const error = await br.click().catch((error_) => {
return error_;
});
expect(error.message).toBe(
'Node is either not clickable or not an HTMLElement'
);
@ -267,17 +280,19 @@ describe('ElementHandle specs', function () {
await page.setContent(
'<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();
const innerWaitFor = element.waitForSelector('.bar');
await element.evaluate((el) => {
el.innerHTML = '<div class="bar">bar1</div>';
});
element = await innerWaitFor;
element = (await innerWaitFor)!;
expect(element).toBeDefined();
expect(
await element.evaluate((el: HTMLElement) => el.innerText)
await element.evaluate((el) => {
return (el as HTMLElement).innerText;
})
).toStrictEqual('bar1');
});
});
@ -298,14 +313,18 @@ describe('ElementHandle specs', function () {
</div>`
);
const el2 = await page.waitForSelector('#el1');
const el2 = (await page.waitForSelector('#el1'))!;
expect(
await (await el2.waitForXPath('//div')).evaluate((el) => el.id)
await (await el2.waitForXPath('//div'))!.evaluate((el) => {
return el.id;
})
).toStrictEqual('el2');
expect(
await (await el2.waitForXPath('.//div')).evaluate((el) => el.id)
await (await el2.waitForXPath('.//div'))!.evaluate((el) => {
return el.id;
})
).toStrictEqual('el2');
});
});
@ -315,10 +334,12 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/scrollable.html');
const button = await page.$('#button-6');
const button = (await page.$('#button-6'))!;
await button.hover();
expect(
await page.evaluate(() => document.querySelector('button:hover').id)
await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6');
});
});
@ -329,7 +350,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html');
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.
const visible = i < 10;
expect(await button.isIntersectingViewport()).toBe(visible);
@ -341,7 +362,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html');
// a button almost cannot be seen
// sometimes we expect to return false by isIntersectingViewport1
const button = await page.$('#btn11');
const button = (await page.$('#btn11'))!;
expect(
await button.isIntersectingViewport({
threshold: 0.001,
@ -354,7 +375,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html');
// a button almost cannot be seen
// sometimes we expect to return false by isIntersectingViewport1
const button = await page.$('#btn0');
const button = (await page.$('#btn0'))!;
expect(
await button.isIntersectingViewport({
threshold: 1,
@ -374,15 +395,15 @@ describe('ElementHandle specs', function () {
// Register.
puppeteer.registerCustomQueryHandler('getById', {
queryOne: (element, selector) =>
document.querySelector(`[id="${selector}"]`),
queryOne: (_element, selector) => {
return document.querySelector(`[id="${selector}"]`);
},
});
const element = await page.$('getById/foo');
const element = (await page.$('getById/foo'))!;
expect(
await page.evaluate<(element: HTMLElement) => string>(
(element) => element.id,
element
)
await page.evaluate<(element: HTMLElement) => string>((element) => {
return element.id;
}, element)
).toBe('foo');
const handlerNamesAfterRegistering = puppeteer.customQueryHandlerNames();
expect(handlerNamesAfterRegistering.includes('getById')).toBeTruthy();
@ -407,7 +428,9 @@ describe('ElementHandle specs', function () {
try {
const { puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('1/2/3', {
queryOne: () => document.querySelector('foo'),
queryOne: () => {
return document.querySelector('foo');
},
});
throw new Error(
'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>'
);
puppeteer.registerCustomQueryHandler('getByClass', {
queryAll: (element, selector) =>
document.querySelectorAll(`.${selector}`),
queryAll: (_element, selector) => {
return document.querySelectorAll(`.${selector}`);
},
});
const elements = await page.$$('getByClass/foo');
const classNames = await Promise.all(
elements.map(
async (element) =>
await page.evaluate<(element: HTMLElement) => string>(
(element) => element.className,
elements.map(async (element) => {
return await page.evaluate<(element: HTMLElement) => string>(
(element) => {
return element.className;
},
element
)
)
);
})
);
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>'
);
puppeteer.registerCustomQueryHandler('getByClass', {
queryAll: (element, selector) =>
document.querySelectorAll(`.${selector}`),
queryAll: (_element, 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);
});
it('should wait correctly with waitForSelector', async () => {
const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`),
queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
});
const waitFor = page.waitForSelector('getByClass/foo');
@ -475,7 +502,9 @@ describe('ElementHandle specs', function () {
it('should wait correctly with waitForSelector on an element', async () => {
const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`),
queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
});
const waitFor = page.waitForSelector('getByClass/foo');
@ -483,7 +512,7 @@ describe('ElementHandle specs', function () {
await page.setContent(
'<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();
const innerWaitFor = element.waitForSelector('getByClass/bar');
@ -492,10 +521,12 @@ describe('ElementHandle specs', function () {
el.innerHTML = '<div class="bar">bar1</div>';
});
element = await innerWaitFor;
element = (await innerWaitFor)!;
expect(element).toBeDefined();
expect(
await element.evaluate((el: HTMLElement) => el.innerText)
await element.evaluate((el) => {
return (el as HTMLElement).innerText;
})
).toStrictEqual('bar1');
});
@ -504,7 +535,9 @@ describe('ElementHandle specs', function () {
sinon.stub(console, 'warn').callsFake(() => {});
const { page, puppeteer } = getTestState();
puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`),
queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
});
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>'
);
puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`),
queryAll: (element, selector) =>
element.querySelectorAll(`.${selector}`),
queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
queryAll: (element, selector) => {
return element.querySelectorAll(`.${selector}`);
},
});
const element = await page.$('getByClass/foo');
const element = (await page.$('getByClass/foo'))!;
expect(element).toBeDefined();
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>'
);
puppeteer.registerCustomQueryHandler('getByClass', {
queryOne: (element, selector) => element.querySelector(`.${selector}`),
queryAll: (element, selector) =>
element.querySelectorAll(`.${selector}`),
queryOne: (element, selector) => {
return element.querySelector(`.${selector}`);
},
queryAll: (element, selector) => {
return element.querySelectorAll(`.${selector}`);
},
});
const txtContent = await page.$eval(
'getByClass/foo',
(div) => div.textContent
);
const txtContent = await page.$eval('getByClass/foo', (div) => {
return div.textContent;
});
expect(txtContent).toBe('text');
const txtContents = await page.$$eval('getByClass/foo', (divs) =>
divs.map((d) => d.textContent).join('')
);
const txtContents = await page.$$eval('getByClass/foo', (divs) => {
return divs
.map((d) => {
return d.textContent;
})
.join('');
});
expect(txtContents).toBe('textcontent');
});
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
// @ts-nocheck
/**
* Copyright 2017 Google Inc. All rights reserved.
*
@ -14,45 +13,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const fs = require('fs');
const Diff = require('text-diff');
const mime = require('mime');
const PNG = require('pngjs').PNG;
const jpeg = require('jpeg-js');
const pixelmatch = require('pixelmatch');
import assert from 'assert';
import diff from 'diff';
import fs from 'fs';
import jpeg from 'jpeg-js';
import mime from 'mime';
import path from 'path';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
module.exports = { compare };
interface DiffFile {
diff: string | Buffer;
ext?: string;
}
const GoldenComparators = {
'image/png': compareImages,
'image/jpeg': compareImages,
'text/plain': compareText,
const GoldenComparators = new Map<
string,
(
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));
};
/**
* @param {?Object} actualBuffer
* @param {!Buffer} expectedBuffer
* @param {!string} mimeType
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
*/
function compareImages(actualBuffer, expectedBuffer, mimeType) {
if (!actualBuffer || !(actualBuffer instanceof Buffer)) {
return { errorMessage: 'Actual result should be Buffer.' };
}
const compareImages = (
actualBuffer: string | Buffer,
expectedBuffer: string | Buffer,
mimeType: string
): DiffFile | undefined => {
assert(typeof actualBuffer !== 'string');
assert(typeof expectedBuffer !== 'string');
const actual =
mimeType === 'image/png'
? PNG.sync.read(actualBuffer)
: jpeg.decode(actualBuffer);
const expected =
mimeType === 'image/png'
? PNG.sync.read(expectedBuffer)
: jpeg.decode(expectedBuffer);
if (expected.width !== actual.width || expected.height !== actual.height) {
return {
errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. `,
};
throw new Error(
`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 count = pixelmatch(
@ -63,64 +78,68 @@ function compareImages(actualBuffer, expectedBuffer, mimeType) {
expected.height,
{ threshold: 0.1 }
);
return count > 0 ? { diff: PNG.sync.write(diff) } : null;
}
return count > 0 ? { diff: PNG.sync.write(diff) } : undefined;
};
/**
* @param {?Object} actual
* @param {!Buffer} expectedBuffer
* @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
*/
function compareText(actual, expectedBuffer) {
if (typeof actual !== 'string') {
return { errorMessage: 'Actual result should be string' };
}
const compareText = (
actual: string | Buffer,
expectedBuffer: string | Buffer
): DiffFile | undefined => {
assert(typeof actual === 'string');
const expected = expectedBuffer.toString('utf-8');
if (expected === actual) {
return null;
return;
}
const diff = new Diff();
const result = diff.main(expected, actual);
diff.cleanupSemantic(result);
let html = diff.prettyHtml(result);
const diffStylePath = path.join(__dirname, 'diffstyle.css');
html = `<link rel="stylesheet" href="file://${diffStylePath}">` + html;
const result = diff.diffLines(expected, actual);
const html = result.reduce((text, change) => {
text += change.added
? `<span class='ins'>${change.value}</span>`
: change.removed
? `<span class='del'>${change.value}</span>`
: change.value;
return text;
}, `<link rel="stylesheet" href="file://${path.join(__dirname, 'diffstyle.css')}">`);
return {
diff: html,
diffExtension: '.html',
ext: '.html',
};
}
};
/**
* @param {?Object} actual
* @param {string} goldenName
* @returns {!{pass: boolean, message: (undefined|string)}}
*/
function compare(goldenPath, outputPath, actual, goldenName) {
GoldenComparators.set('image/png', compareImages);
GoldenComparators.set('image/jpeg', compareImages);
GoldenComparators.set('text/plain', compareText);
export const compare = (
goldenPath: string,
outputPath: string,
actual: string | Buffer,
goldenName: string
): { pass: true } | { pass: false; message: string } => {
goldenPath = path.normalize(goldenPath);
outputPath = path.normalize(outputPath);
const expectedPath = path.join(goldenPath, goldenName);
const actualPath = path.join(outputPath, goldenName);
const messageSuffix =
'Output is saved in "' + path.basename(outputPath + '" directory');
const messageSuffix = `Output is saved in "${path.basename(
outputPath + '" directory'
)}`;
if (!fs.existsSync(expectedPath)) {
ensureOutputDir();
fs.writeFileSync(actualPath, actual);
return {
pass: false,
message: goldenName + ' is missing in golden results. ' + messageSuffix,
message: `${goldenName} is missing in golden results. ${messageSuffix}`,
};
}
const expected = fs.readFileSync(expectedPath);
const mimeType = mime.getType(goldenName);
const comparator = GoldenComparators[mimeType];
assert(mimeType);
const comparator = GoldenComparators.get(mimeType);
if (!comparator) {
return {
pass: false,
message:
'Failed to find comparator with type ' + mimeType + ': ' + goldenName,
message: `Failed to find comparator with type ${mimeType}: ${goldenName}`,
};
}
const result = comparator(actual, expected, mimeType);
@ -135,18 +154,14 @@ function compare(goldenPath, outputPath, actual, goldenName) {
// Copy expected to the output/ folder for convenience.
fs.writeFileSync(addSuffix(actualPath, '-expected'), expected);
}
if (result.diff) {
const diffPath = addSuffix(actualPath, '-diff', result.diffExtension);
if (result) {
const diffPath = addSuffix(actualPath, '-diff', result.ext);
fs.writeFileSync(diffPath, result.diff);
}
let message = goldenName + ' mismatch!';
if (result.errorMessage) {
message += ' ' + result.errorMessage;
}
return {
pass: false,
message: message + ' ' + messageSuffix,
message: `${goldenName} mismatch! ${messageSuffix}`,
};
function ensureOutputDir() {
@ -154,17 +169,4 @@ function compare(goldenPath, outputPath, actual, goldenName) {
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.
*/
import path from 'path';
import os from 'os';
import fs from 'fs';
import { promisify } from 'util';
import expect from 'expect';
import {
getTestState,
describeChromeOnly,
itFailsWindows,
} from './mocha-utils'; // eslint-disable-line import/extensions
import fs from 'fs';
import os from 'os';
import path from 'path';
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 mkdtempAsync = promisify(fs.mkdtemp);
@ -39,12 +43,22 @@ describeChromeOnly('headful tests', function () {
*/
this.timeout(20 * 1000);
let headfulOptions;
let headlessOptions;
let extensionOptions;
let forcedOopifOptions;
let devtoolsOptions;
const browsers = [];
let headfulOptions: PuppeteerLaunchOptions | undefined;
let headlessOptions: PuppeteerLaunchOptions & { headless: boolean };
let extensionOptions: PuppeteerLaunchOptions & {
headless: boolean;
args: string[];
};
let forcedOopifOptions: PuppeteerLaunchOptions & {
headless: boolean;
devtools: boolean;
args: string[];
};
let devtoolsOptions: PuppeteerLaunchOptions & {
headless: boolean;
devtools: boolean;
};
const browsers: any[] = [];
beforeEach(() => {
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);
browsers.push(browser);
return browser;
@ -106,7 +120,9 @@ describeChromeOnly('headful tests', function () {
);
const page = await browserWithExtension.newPage();
const backgroundPageTarget = await browserWithExtension.waitForTarget(
(target) => target.type() === 'background_page'
(target: { type: () => string }) => {
return target.type() === 'background_page';
}
);
await page.close();
await browserWithExtension.close();
@ -119,11 +135,21 @@ describeChromeOnly('headful tests', function () {
extensionOptions
);
const backgroundPageTarget = await browserWithExtension.waitForTarget(
(target) => target.type() === 'background_page'
(target: { type: () => string }) => {
return target.type() === 'background_page';
}
);
const page = await backgroundPageTarget.page();
expect(await page.evaluate(() => 2 * 3)).toBe(6);
expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42);
const page = (await backgroundPageTarget.page())!;
expect(
await page.evaluate(() => {
return 2 * 3;
})
).toBe(6);
expect(
await page.evaluate(() => {
return (globalThis as any).MAGIC;
})
).toBe(42);
await browserWithExtension.close();
});
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(
(target) => target.type() === 'other'
);
const page = await devtoolsPageTarget.page();
expect(await page.evaluate(() => 2 * 3)).toBe(6);
const devtoolsPageTarget = await browser.waitForTarget((target) => {
return target.type() === 'other';
});
const page = (await devtoolsPageTarget.page())!;
expect(
await page.evaluate(() => {
return 2 * 3;
})
).toBe(6);
expect(await browser.pages()).toContainEqual(page);
await browser.close();
});
it('should have default url when launching browser', async function () {
const { puppeteer } = getTestState();
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']);
await browser.close();
});
@ -169,11 +201,10 @@ describeChromeOnly('headful tests', function () {
);
const headfulPage = await headfulBrowser.newPage();
await headfulPage.goto(server.EMPTY_PAGE);
await headfulPage.evaluate(
() =>
(document.cookie =
'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT')
);
await headfulPage.evaluate(() => {
return (document.cookie =
'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
});
await headfulBrowser.close();
// Read the cookie from headless chrome
const headlessBrowser = await launchBrowser(
@ -182,7 +213,9 @@ describeChromeOnly('headful tests', function () {
);
const headlessPage = await headlessBrowser.newPage();
await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie);
const cookie = await headlessPage.evaluate(() => {
return document.cookie;
});
await headlessBrowser.close();
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
await rmAsync(userDataDir).catch(() => {});
@ -198,17 +231,23 @@ describeChromeOnly('headful tests', function () {
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
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(() => {
const frame = document.createElement('iframe');
frame.setAttribute('src', 'https://google.com/');
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/"]');
const urls = page
.frames()
.map((frame) => frame.url())
.map((frame: { url: () => any }) => {
return frame.url();
})
.sort();
expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']);
await browser.close();
@ -221,26 +260,32 @@ describeChromeOnly('headful tests', function () {
// Setup our session listeners to observe OOPIF activity.
const session = await page.target().createCDPSession();
const networkEvents = [];
const otherSessions = [];
const networkEvents: any[] = [];
const otherSessions: any[] = [];
await session.send('Target.setAutoAttach', {
autoAttach: true,
flatten: 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);
session.on('Network.requestWillBeSent', (params) =>
networkEvents.push(params)
);
session.on('Network.requestWillBeSent', (params: any) => {
return networkEvents.push(params);
});
await session.send('Network.enable');
await session.send('Runtime.runIfWaitingForDebugger');
});
}
);
// Navigate to the empty page and add an OOPIF iframe with at least one request.
await page.goto(server.EMPTY_PAGE);
await page.evaluate((frameUrl) => {
await page.evaluate((frameUrl: string) => {
const frame = document.createElement('iframe');
frame.setAttribute('src', frameUrl);
document.body.appendChild(frame);
@ -255,14 +300,16 @@ describeChromeOnly('headful tests', function () {
expect(otherSessions).toHaveLength(1);
// Resume the iframe and trigger another request.
const iframeSession = otherSessions[0];
const iframeSession = otherSessions[0]!;
await iframeSession.send('Runtime.evaluate', {
expression: `fetch('/fetch')`,
awaitPromise: true,
});
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`);
});
it('should close browser with beforeunload page', async () => {
@ -286,7 +333,9 @@ describeChromeOnly('headful tests', function () {
const context = await browser.createIncognitoBrowserContext();
await Promise.all([
context.newPage(),
browser.waitForTarget((target) => target.url().includes('devtools://')),
browser.waitForTarget((target: { url: () => string | string[] }) => {
return target.url().includes('devtools://');
}),
]);
await browser.close();
});
@ -300,20 +349,28 @@ describeChromeOnly('headful tests', function () {
const page2 = await browser.newPage();
await page1.bringToFront();
expect(await page1.evaluate(() => document.visibilityState)).toBe(
'visible'
);
expect(await page2.evaluate(() => document.visibilityState)).toBe(
'hidden'
);
expect(
await page1.evaluate(() => {
return document.visibilityState;
})
).toBe('visible');
expect(
await page2.evaluate(() => {
return document.visibilityState;
})
).toBe('hidden');
await page2.bringToFront();
expect(await page1.evaluate(() => document.visibilityState)).toBe(
'hidden'
);
expect(await page2.evaluate(() => document.visibilityState)).toBe(
'visible'
);
expect(
await page1.evaluate(() => {
return document.visibilityState;
})
).toBe('hidden');
expect(
await page2.evaluate(() => {
return document.visibilityState;
})
).toBe('visible');
await page1.close();
await page2.close();
@ -340,7 +397,7 @@ describeChromeOnly('headful tests', function () {
const promises = [];
for (let i = 0; i < N; ++i) {
promises.push(
pages[i].screenshot({
pages[i]!.screenshot({
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) {
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();
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import {
setupTestBrowserHooks,
setupTestPageAndContextHooks,
itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
import { KeyInput } from '../../lib/cjs/puppeteer/common/USKeyboardLayout.js';
interface Dimensions {
@ -31,7 +31,7 @@ interface Dimensions {
}
function dimensions(): Dimensions {
const rect = document.querySelector('textarea').getBoundingClientRect();
const rect = document.querySelector('textarea')!.getBoundingClientRect();
return {
x: rect.left,
y: rect.top,
@ -47,7 +47,7 @@ describe('Mouse', function () {
const { page } = getTestState();
await page.evaluate(() => {
globalThis.clickPromise = new Promise((resolve) => {
(globalThis as any).clickPromise = new Promise((resolve) => {
document.addEventListener('click', (event) => {
resolve({
type: event.type,
@ -61,9 +61,9 @@ describe('Mouse', function () {
});
});
await page.mouse.click(50, 60);
const event = await page.evaluate<() => MouseEvent>(
() => globalThis.clickPromise
);
const event = await page.evaluate<() => MouseEvent>(() => {
return (globalThis as any).clickPromise;
});
expect(event.type).toBe('click');
expect(event.detail).toBe(1);
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.";
await page.keyboard.type(text);
// 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(
() => (document.querySelector('textarea').scrollTop = 0)
);
await page.evaluate(() => {
return new Promise(requestAnimationFrame);
});
await page.evaluate(() => {
return (document.querySelector('textarea')!.scrollTop = 0);
});
const { x, y } = await page.evaluate(dimensions);
await page.mouse.move(x + 2, y + 2);
await page.mouse.down();
@ -107,7 +109,7 @@ describe('Mouse', function () {
await page.mouse.up();
expect(
await page.evaluate(() => {
const textarea = document.querySelector('textarea');
const textarea = document.querySelector('textarea')!;
return textarea.value.substring(
textarea.selectionStart,
textarea.selectionEnd
@ -121,15 +123,21 @@ describe('Mouse', function () {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.hover('#button-6');
expect(
await page.evaluate(() => document.querySelector('button:hover').id)
await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6');
await page.hover('#button-2');
expect(
await page.evaluate(() => document.querySelector('button:hover').id)
await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-2');
await page.hover('#button-91');
expect(
await page.evaluate(() => document.querySelector('button:hover').id)
await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-91');
});
itFailsFirefox(
@ -138,10 +146,15 @@ describe('Mouse', function () {
const { page, server } = getTestState();
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');
expect(
await page.evaluate(() => document.querySelector('button:hover').id)
await page.evaluate(() => {
return document.querySelector('button:hover')!.id;
})
).toBe('button-6');
}
);
@ -149,11 +162,15 @@ describe('Mouse', function () {
const { page, server, isFirefox } = getTestState();
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() =>
document
.querySelector('#button-3')
.addEventListener('mousedown', (e) => (globalThis.lastEvent = e), true)
await page.evaluate(() => {
return document.querySelector('#button-3')!.addEventListener(
'mousedown',
(e) => {
return ((globalThis as any).lastEvent = e);
},
true
);
});
const modifiers = new Map<KeyInput, string>([
['Shift', 'shiftKey'],
['Control', 'ctrlKey'],
@ -162,13 +179,15 @@ describe('Mouse', function () {
]);
// In Firefox, the Meta modifier only exists on Mac
if (isFirefox && os.platform() !== 'darwin') {
delete modifiers['Meta'];
modifiers.delete('Meta');
}
for (const [modifier, key] of modifiers) {
await page.keyboard.down(modifier);
await page.click('#button-3');
if (
!(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
!(await page.evaluate((mod: string) => {
return (globalThis as any).lastEvent[mod];
}, key))
) {
throw new Error(key + ' should be true');
}
@ -177,9 +196,11 @@ describe('Mouse', function () {
await page.click('#button-3');
for (const [modifier, key] of modifiers) {
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();
await page.goto(server.PREFIX + '/input/wheel.html');
const elem = await page.$('div');
const boundingBoxBefore = await elem.boundingBox();
const elem = (await page.$('div'))!;
const boundingBoxBefore = (await elem.boundingBox())!;
expect(boundingBoxBefore).toMatchObject({
width: 115,
height: 115,
@ -211,9 +232,9 @@ describe('Mouse', function () {
await page.mouse.move(100, 100);
await page.evaluate(() => {
globalThis.result = [];
(globalThis as any).result = [];
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 });
@ -234,7 +255,7 @@ describe('Mouse', function () {
await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html');
await page.evaluate(() => {
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,
itFailsFirefox,
describeFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
import os from 'os';
import { ServerResponse } from 'http';
import { HTTPRequest } from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
describe('navigation', function () {
setupTestBrowserHooks();
@ -62,31 +64,35 @@ describe('navigation', function () {
it('should return response when page changes its URL after load', async () => {
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);
});
itFailsFirefox('should work with subframes return 204', async () => {
const { page, server } = getTestState();
server.setRoute('/frames/frame.html', (req, res) => {
server.setRoute('/frames/frame.html', (_req, res) => {
res.statusCode = 204;
res.end();
});
let error = null;
let error!: Error;
await page
.goto(server.PREFIX + '/frames/one-frame.html')
.catch((error_) => (error = error_));
expect(error).toBe(null);
.catch((error_) => {
return (error = error_);
});
expect(error).toBeUndefined();
});
itFailsFirefox('should fail when server returns 204', async () => {
const { page, server, isChrome } = getTestState();
server.setRoute('/empty.html', (req, res) => {
server.setRoute('/empty.html', (_req, res) => {
res.statusCode = 204;
res.end();
});
let error = null;
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).not.toBe(null);
if (isChrome) {
expect(error.message).toContain('net::ERR_ABORTED');
@ -100,7 +106,7 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'domcontentloaded',
});
expect(response.status()).toBe(200);
expect(response!.status()).toBe(200);
});
it('should work when page calls history API in beforeunload', async () => {
const { page, server } = getTestState();
@ -109,12 +115,14 @@ describe('navigation', function () {
await page.evaluate(() => {
window.addEventListener(
'beforeunload',
() => history.replaceState(null, 'initial', window.location.href),
() => {
return history.replaceState(null, 'initial', window.location.href);
},
false
);
});
const response = await page.goto(server.PREFIX + '/grid.html');
expect(response.status()).toBe(200);
expect(response!.status()).toBe(200);
});
itFailsFirefox(
'should navigate to empty page with networkidle0',
@ -124,7 +132,7 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'networkidle0',
});
expect(response.status()).toBe(200);
expect(response!.status()).toBe(200);
}
);
itFailsFirefox(
@ -135,14 +143,16 @@ describe('navigation', function () {
const response = await page.goto(server.EMPTY_PAGE, {
waitUntil: 'networkidle2',
});
expect(response.status()).toBe(200);
expect(response!.status()).toBe(200);
}
);
itFailsFirefox('should fail when navigating to bad url', async () => {
const { page, isChrome } = getTestState();
let error = null;
await page.goto('asdfasdf').catch((error_) => (error = error_));
let error!: Error;
await page.goto('asdfasdf').catch((error_) => {
return (error = error_);
});
if (isChrome) {
expect(error.message).toContain('Cannot navigate to invalid URL');
} else {
@ -165,15 +175,21 @@ describe('navigation', function () {
// Make sure that network events do not emit 'undefined'.
// @see https://crbug.com/750469
const requests = [];
page.on('request', () => requests.push('request'));
page.on('requestfinished', () => requests.push('requestfinished'));
page.on('requestfailed', () => requests.push('requestfailed'));
const requests: string[] = [];
page.on('request', () => {
return requests.push('request');
});
page.on('requestfinished', () => {
return requests.push('requestfinished');
});
page.on('requestfailed', () => {
return requests.push('requestfailed');
});
let error = null;
await page
.goto(httpsServer.EMPTY_PAGE)
.catch((error_) => (error = error_));
let error!: Error;
await page.goto(httpsServer.EMPTY_PAGE).catch((error_) => {
return (error = error_);
});
if (isChrome) {
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
} else {
@ -181,18 +197,20 @@ describe('navigation', function () {
}
expect(requests.length).toBe(2);
expect(requests[0]).toBe('request');
expect(requests[1]).toBe('requestfailed');
expect(requests[0]!).toBe('request');
expect(requests[1]!).toBe('requestfailed');
});
it('should fail when navigating to bad SSL after redirects', async () => {
const { page, server, httpsServer, isChrome } = getTestState();
server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/empty.html');
let error = null;
let error!: Error;
await page
.goto(httpsServer.PREFIX + '/redirect/1.html')
.catch((error_) => (error = error_));
.catch((error_) => {
return (error = error_);
});
if (isChrome) {
expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
} else {
@ -202,11 +220,13 @@ describe('navigation', function () {
it('should throw if networkidle is passed as an option', async () => {
const { page, server } = getTestState();
let error = null;
let error!: Error;
await page
// @ts-expect-error purposefully passing an old option
.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' })
.catch((error_) => (error = error_));
.catch((error_) => {
return (error = error_);
});
expect(error.message).toContain(
'"networkidle" option is no longer supported'
);
@ -214,10 +234,12 @@ describe('navigation', function () {
it('should fail when main resources failed to load', async () => {
const { page, isChrome } = getTestState();
let error = null;
let error!: Error;
await page
.goto('http://localhost:44123/non-existing-url')
.catch((error_) => (error = error_));
.catch((error_) => {
return (error = error_);
});
if (isChrome) {
expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
} else {
@ -229,10 +251,12 @@ describe('navigation', function () {
// Hang for request to the empty.html
server.setRoute('/empty.html', () => {});
let error = null;
let error!: Error;
await page
.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).toBeInstanceOf(puppeteer.errors.TimeoutError);
});
@ -241,11 +265,11 @@ describe('navigation', function () {
// Hang for request to the empty.html
server.setRoute('/empty.html', () => {});
let error = null;
let error!: Error;
page.setDefaultNavigationTimeout(1);
await page
.goto(server.PREFIX + '/empty.html')
.catch((error_) => (error = error_));
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
});
@ -254,11 +278,11 @@ describe('navigation', function () {
// Hang for request to the empty.html
server.setRoute('/empty.html', () => {});
let error = null;
let error!: Error;
page.setDefaultTimeout(1);
await page
.goto(server.PREFIX + '/empty.html')
.catch((error_) => (error = error_));
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
});
@ -267,43 +291,47 @@ describe('navigation', function () {
// Hang for request to the empty.html
server.setRoute('/empty.html', () => {});
let error = null;
let error!: Error;
page.setDefaultTimeout(0);
page.setDefaultNavigationTimeout(1);
await page
.goto(server.PREFIX + '/empty.html')
.catch((error_) => (error = error_));
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
return (error = error_);
});
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
});
it('should disable timeout when its set to 0', async () => {
const { page, server } = getTestState();
let error = null;
let error!: Error;
let loaded = false;
page.once('load', () => (loaded = true));
page.once('load', () => {
return (loaded = true);
});
await page
.goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] })
.catch((error_) => (error = error_));
expect(error).toBe(null);
.catch((error_) => {
return (error = error_);
});
expect(error).toBeUndefined();
expect(loaded).toBe(true);
});
it('should work when navigating to valid url', async () => {
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);
});
itFailsFirefox('should work when navigating to data url', async () => {
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);
});
it('should work when navigating to 404', async () => {
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.status()).toBe(404);
});
@ -313,7 +341,7 @@ describe('navigation', function () {
server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/redirect/3.html');
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.url()).toBe(server.EMPTY_PAGE);
});
@ -322,20 +350,20 @@ describe('navigation', function () {
async () => {
const { page, server } = getTestState();
let responses = [];
let responses: ServerResponse[] = [];
// Hold on to a bunch of requests without answering.
server.setRoute('/fetch-request-a.js', (req, res) =>
responses.push(res)
);
server.setRoute('/fetch-request-b.js', (req, res) =>
responses.push(res)
);
server.setRoute('/fetch-request-c.js', (req, res) =>
responses.push(res)
);
server.setRoute('/fetch-request-d.js', (req, res) =>
responses.push(res)
);
server.setRoute('/fetch-request-a.js', (_req, res) => {
return responses.push(res);
});
server.setRoute('/fetch-request-b.js', (_req, res) => {
return responses.push(res);
});
server.setRoute('/fetch-request-c.js', (_req, res) => {
return responses.push(res);
});
server.setRoute('/fetch-request-d.js', (_req, res) => {
return responses.push(res);
});
const initialFetchResourcesRequested = Promise.all([
server.waitForRequest('/fetch-request-a.js'),
server.waitForRequest('/fetch-request-b.js'),
@ -355,10 +383,14 @@ describe('navigation', function () {
);
// Track when the navigation gets completed.
let navigationFinished = false;
navigationPromise.then(() => (navigationFinished = true));
navigationPromise.then(() => {
return (navigationFinished = true);
});
// 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);
// Wait for the initial three resources to be requested.
@ -387,7 +419,7 @@ describe('navigation', function () {
response.end(`File not found`);
}
const response = await navigationPromise;
const response = (await navigationPromise)!;
// Expect navigation to succeed.
expect(response.ok()).toBe(true);
}
@ -396,7 +428,9 @@ describe('navigation', function () {
const { page, server } = getTestState();
let warning = null;
const warningHandler = (w) => (warning = w);
const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler);
for (let i = 0; i < 20; ++i) {
await page.goto(server.EMPTY_PAGE);
@ -408,7 +442,9 @@ describe('navigation', function () {
const { page } = getTestState();
let warning = null;
const warningHandler = (w) => (warning = w);
const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler);
for (let i = 0; i < 20; ++i) {
await page.goto('asdf').catch(() => {
@ -422,7 +458,9 @@ describe('navigation', function () {
const { context, server } = getTestState();
let warning = null;
const warningHandler = (w) => (warning = w);
const warningHandler: NodeJS.WarningListener = (w) => {
return (warning = w);
};
process.on('warning', warningHandler);
await Promise.all(
[...Array(20)].map(async () => {
@ -439,16 +477,15 @@ describe('navigation', function () {
async () => {
const { page } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
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(requests.length).toBe(1);
expect(requests[0].url()).toBe(dataURL);
expect(requests[0]!.url()).toBe(dataURL);
}
);
itFailsFirefox(
@ -456,22 +493,21 @@ describe('navigation', function () {
async () => {
const { page, server } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
const response = await page.goto(server.EMPTY_PAGE + '#hash');
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
const response = (await page.goto(server.EMPTY_PAGE + '#hash'))!;
expect(response.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE);
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 () => {
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.url()).toContain('self-request.html');
});
@ -479,11 +515,11 @@ describe('navigation', function () {
const { page, httpsServer } = getTestState();
const url = httpsServer.PREFIX + '/redirect/1.html';
let error = null;
let error!: Error;
try {
await page.goto(url);
} catch (error_) {
error = error_;
error = error_ as Error;
}
expect(error.message).toContain(url);
});
@ -510,19 +546,20 @@ describe('navigation', function () {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForNavigation(),
page.evaluate(
(url: string) => (window.location.href = url),
server.PREFIX + '/grid.html'
),
page.evaluate((url: string) => {
return (window.location.href = url);
}, server.PREFIX + '/grid.html'),
]);
expect(response.ok()).toBe(true);
expect(response.url()).toContain('grid.html');
expect(response!.ok()).toBe(true);
expect(response!.url()).toContain('grid.html');
});
it('should work with both domcontentloaded and load', async () => {
const { page, server } = getTestState();
let response = null;
server.setRoute('/one-style.css', (req, res) => (response = res));
let response!: ServerResponse;
server.setRoute('/one-style.css', (_req, res) => {
return (response = res);
});
const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
const domContentLoadedPromise = page.waitForNavigation({
waitUntil: 'domcontentloaded',
@ -533,7 +570,9 @@ describe('navigation', function () {
.waitForNavigation({
waitUntil: ['load', 'domcontentloaded'],
})
.then(() => (bothFired = true));
.then(() => {
return (bothFired = true);
});
await server.waitForRequest('/one-style.css');
await domContentLoadedPromise;
@ -637,7 +676,9 @@ describe('navigation', function () {
});
});
await Promise.all([
frame.evaluate(() => window.stop()),
frame.evaluate(() => {
return window.stop();
}),
navigationPromise,
]);
}
@ -651,15 +692,15 @@ describe('navigation', function () {
await page.goto(server.EMPTY_PAGE);
await page.goto(server.PREFIX + '/grid.html');
let response = await page.goBack();
let response = (await page.goBack())!;
expect(response.ok()).toBe(true);
expect(response.url()).toContain(server.EMPTY_PAGE);
response = await page.goForward();
response = (await page.goForward())!;
expect(response.ok()).toBe(true);
expect(response.url()).toContain('/grid.html');
response = await page.goForward();
response = (await page.goForward())!;
expect(response).toBe(null);
});
itFailsFirefox('should work with HistoryAPI', async () => {
@ -686,12 +727,12 @@ describe('navigation', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/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()[0]!.url()).toContain('/frames/one-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.frame()).toBe(page.frames()[1]);
expect(response.frame()).toBe(page.frames()[1]!);
});
it('should reject when frame detaches', async () => {
const { page, server } = getTestState();
@ -700,12 +741,16 @@ describe('navigation', function () {
server.setRoute('/empty.html', () => {});
const navigationPromise = page
.frames()[1]
.frames()[1]!
.goto(server.EMPTY_PAGE)
.catch((error_) => error_);
.catch((error_) => {
return error_;
});
await server.waitForRequest('/empty.html');
await page.$eval('iframe', (frame) => frame.remove());
await page.$eval('iframe', (frame) => {
return frame.remove();
});
const error = await navigationPromise;
expect(error.message).toBe('Navigating frame was detached');
});
@ -722,20 +767,20 @@ describe('navigation', function () {
utils.attachFrame(page, 'frame3', server.EMPTY_PAGE),
]);
// Navigate all frames to the same URL.
const serverResponses = [];
server.setRoute('/one-style.html', (req, res) =>
serverResponses.push(res)
);
const serverResponses: ServerResponse[] = [];
server.setRoute('/one-style.html', (_req, res) => {
return serverResponses.push(res);
});
const navigations = [];
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');
}
// Respond from server out-of-order.
const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
for (const i of [1, 2, 0]) {
serverResponses[i].end(serverResponseTexts[i]);
const response = await navigations[i];
serverResponses[i]!.end(serverResponseTexts[i]);
const response = (await navigations[i])!;
expect(response.frame()).toBe(frames[i]);
expect(await response.text()).toBe(serverResponseTexts[i]);
}
@ -747,35 +792,38 @@ describe('navigation', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1];
const frame = page.frames()[1]!;
const [response] = await Promise.all([
frame.waitForNavigation(),
frame.evaluate(
(url: string) => (window.location.href = url),
server.PREFIX + '/grid.html'
),
frame.evaluate((url: string) => {
return (window.location.href = url);
}, server.PREFIX + '/grid.html'),
]);
expect(response.ok()).toBe(true);
expect(response.url()).toContain('grid.html');
expect(response.frame()).toBe(frame);
expect(response!.ok()).toBe(true);
expect(response!.url()).toContain('grid.html');
expect(response!.frame()).toBe(frame);
expect(page.url()).toContain('/frames/one-frame.html');
});
it('should fail when frame detaches', async () => {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1];
const frame = page.frames()[1]!;
server.setRoute('/empty.html', () => {});
let error = null;
const navigationPromise = frame
.waitForNavigation()
.catch((error_) => (error = error_));
let error!: Error;
const navigationPromise = frame.waitForNavigation().catch((error_) => {
return (error = error_);
});
await Promise.all([
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;
expect(error.message).toBe('Navigating frame was detached');
});
@ -786,9 +834,15 @@ describe('navigation', function () {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => (globalThis._foo = 10));
await page.evaluate(() => {
return ((globalThis as any)._foo = 10);
});
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,
itChromeOnly,
itFirefoxOnly,
} from './mocha-utils'; // eslint-disable-line import/extensions
import { HTTPResponse } from '../../lib/cjs/puppeteer/api-docs-entry.js';
} from './mocha-utils.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 () {
setupTestBrowserHooks();
@ -37,36 +39,35 @@ describe('network', function () {
it('should fire for navigation requests', async () => {
const { page, server } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
await page.goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(requests.length).toBe(1);
});
it('should fire for iframes', async () => {
const { page, server } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
await page.goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2);
});
it('should fire for fetches', async () => {
const { page, server } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html'));
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
await page.evaluate(() => {
return fetch('/empty.html');
});
expect(requests.length).toBe(2);
});
});
@ -74,57 +75,56 @@ describe('network', function () {
it('should work for main frame navigation request', async () => {
const { page, server } = getTestState();
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
await page.goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
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 () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
const requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
(await page.goto(server.EMPTY_PAGE))!;
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
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 () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
let requests = [];
page.on(
'request',
(request) => !utils.isFavicon(request) && requests.push(request)
);
await page.evaluate(() => fetch('/digits/1.png'));
requests = requests.filter(
(request) => !request.url().includes('favicon')
);
(await page.goto(server.EMPTY_PAGE))!;
let requests: HTTPRequest[] = [];
page.on('request', (request) => {
return !utils.isFavicon(request) && requests.push(request);
});
await page.evaluate(() => {
return fetch('/digits/1.png');
});
requests = requests.filter((request) => {
return !request.url().includes('favicon');
});
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
expect(requests[0]!.frame()).toBe(page.mainFrame());
});
});
describe('Request.headers', function () {
itChromeOnly('should define Chrome as user agent header', async () => {
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');
});
itFirefoxOnly('should define Firefox as user agent header', async () => {
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');
});
});
@ -133,11 +133,11 @@ describe('network', function () {
it('should work', async () => {
const { page, server } = getTestState();
server.setRoute('/empty.html', (req, res) => {
server.setRoute('/empty.html', (_req, res) => {
res.setHeader('foo', 'bar');
res.end();
});
const response = await page.goto(server.EMPTY_PAGE);
const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.headers()['foo']).toBe('bar');
});
});
@ -147,9 +147,12 @@ describe('network', function () {
const { page, server } = getTestState();
const initiators = new Map();
page.on('request', (request) =>
initiators.set(request.url().split('/').pop(), request.initiator())
page.on('request', (request) => {
return initiators.set(
request.url().split('/').pop(),
request.initiator()
);
});
await page.goto(server.PREFIX + '/initiator.html');
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('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'
);
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'
);
expect(initiators.get('initiator.js').url).toBe(
@ -188,7 +191,7 @@ describe('network', function () {
it('should return |false| for non-cached content', async () => {
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);
});
@ -196,12 +199,12 @@ describe('network', function () {
const { page, server } = getTestState();
const responses = new Map();
page.on(
'response',
(r) =>
page.on('response', (r) => {
return (
!utils.isFavicon(r.request()) &&
responses.set(r.url().split('/').pop(), r)
);
});
// Load and re-load to make sure it's cached.
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 () => {
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);
});
@ -227,16 +230,19 @@ describe('network', function () {
const { page, server } = getTestState();
const responses = new Map();
page.on(
'response',
(r) => !utils.isFavicon(r) && responses.set(r.url().split('/').pop(), r)
page.on('response', (r) => {
return (
!utils.isFavicon(r) && responses.set(r.url().split('/').pop(), r)
);
});
// Load and re-load to make sure serviceworker is installed and running.
await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {
waitUntil: 'networkidle2',
});
await page.evaluate(async () => await globalThis.activationPromise);
await page.evaluate(async () => {
return await (globalThis as any).activationPromise;
});
await page.reload();
expect(responses.size).toBe(2);
@ -251,27 +257,29 @@ describe('network', function () {
it('should work', async () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
server.setRoute('/post', (req, res) => res.end());
let request = null;
(await page.goto(server.EMPTY_PAGE))!;
server.setRoute('/post', (_req, res) => {
return res.end();
});
let request!: HTTPRequest;
page.on('request', (r) => {
if (!utils.isFavicon(r)) {
request = r;
}
});
await page.evaluate(() =>
fetch('./post', {
await page.evaluate(() => {
return fetch('./post', {
method: 'POST',
body: JSON.stringify({ foo: 'bar' }),
})
);
});
});
expect(request).toBeTruthy();
expect(request.postData()).toBe('{"foo":"bar"}');
});
it('should be |undefined| when there is no post data', async () => {
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);
});
});
@ -280,7 +288,7 @@ describe('network', function () {
it('should work', async () => {
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();
expect(responseText).toBe('{"foo": "bar"}');
});
@ -288,7 +296,7 @@ describe('network', function () {
const { page, server } = getTestState();
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');
const responseText = (await response.text()).trimEnd();
expect(responseText).toBe('{"foo": "bar"}');
@ -297,13 +305,15 @@ describe('network', function () {
const { page, server } = getTestState();
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();
expect(redirectChain.length).toBe(1);
const redirected = redirectChain[0].response();
const redirected = redirectChain[0]!.response()!;
expect(redirected.status()).toBe(302);
let error = null;
await redirected.text().catch((error_) => (error = error_));
let error!: Error;
await redirected.text().catch((error_) => {
return (error = error_);
});
expect(error.message).toContain(
'Response body is unavailable for redirect responses'
);
@ -311,10 +321,10 @@ describe('network', function () {
it('should wait until response completes', async () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
(await page.goto(server.EMPTY_PAGE))!;
// Setup server to trap request.
let serverResponse = null;
server.setRoute('/get', (req, res) => {
let serverResponse!: ServerResponse;
server.setRoute('/get', (_req, res) => {
serverResponse = res;
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server.
@ -323,14 +333,17 @@ describe('network', function () {
});
// Setup page to trap response.
let requestFinished = false;
page.on(
'requestfinished',
(r) => (requestFinished = requestFinished || r.url().includes('/get'))
);
page.on('requestfinished', (r) => {
return (requestFinished = requestFinished || r.url().includes('/get'));
});
// send request and wait for server response
const [pageResponse] = await Promise.all([
page.waitForResponse((r) => !utils.isFavicon(r.request())),
page.evaluate(() => fetch('./get', { method: 'GET' })),
page.waitForResponse((r) => {
return !utils.isFavicon(r.request());
}),
page.evaluate(() => {
return fetch('./get', { method: 'GET' });
}),
server.waitForRequest('/get'),
]);
@ -341,9 +354,15 @@ describe('network', function () {
const responseText = pageResponse.text();
// 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.
await new Promise((x) => serverResponse.end('ld!', x));
await new Promise<void>((x) => {
serverResponse.end('ld!', () => {
return x();
});
});
expect(await responseText).toBe('hello world!');
});
});
@ -352,7 +371,7 @@ describe('network', function () {
it('should work', async () => {
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' });
});
});
@ -361,7 +380,7 @@ describe('network', function () {
it('should work', async () => {
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(
path.join(__dirname, '../assets', 'pptr.png')
);
@ -372,7 +391,7 @@ describe('network', function () {
const { page, server } = getTestState();
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(
path.join(__dirname, '../assets', 'pptr.png')
);
@ -384,7 +403,7 @@ describe('network', function () {
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-Headers', 'x-ping');
res.end('Hello World');
@ -422,22 +441,22 @@ describe('network', function () {
it('should work', async () => {
const { page, server } = getTestState();
server.setRoute('/cool', (req, res) => {
server.setRoute('/cool', (_req, res) => {
res.writeHead(200, 'cool!');
res.end();
});
const response = await page.goto(server.PREFIX + '/cool');
const response = (await page.goto(server.PREFIX + '/cool'))!;
expect(response.statusText()).toBe('cool!');
});
it('handles missing status text', async () => {
const { page, server } = getTestState();
server.setRoute('/nostatus', (req, res) => {
server.setRoute('/nostatus', (_req, res) => {
res.writeHead(200, '');
res.end();
});
const response = await page.goto(server.PREFIX + '/nostatus');
const response = (await page.goto(server.PREFIX + '/nostatus'))!;
expect(response.statusText()).toBe('');
});
});
@ -445,11 +464,13 @@ describe('network', function () {
describeFailsFirefox('Response.timing', function () {
it('returns timing information', async () => {
const { page, server } = getTestState();
const responses = [];
page.on('response', (response) => responses.push(response));
await page.goto(server.EMPTY_PAGE);
const responses: HTTPResponse[] = [];
page.on('response', (response) => {
return responses.push(response);
});
(await page.goto(server.EMPTY_PAGE))!;
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 () => {
const { page, server } = getTestState();
const requests = [];
page.on('request', (request) => requests.push(request));
await page.goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
expect(requests[0].resourceType()).toBe('document');
expect(requests[0].method()).toBe('GET');
expect(requests[0].response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
expect(requests[0]!.resourceType()).toBe('document');
expect(requests[0]!.method()).toBe('GET');
expect(requests[0]!.response()).toBeTruthy();
expect(requests[0]!.frame() === page.mainFrame()).toBe(true);
expect(requests[0]!.frame()!.url()).toBe(server.EMPTY_PAGE);
});
it('Page.Events.RequestServedFromCache', async () => {
const { page, server } = getTestState();
const cached = [];
page.on('requestservedfromcache', (r) =>
cached.push(r.url().split('/').pop())
);
const cached: string[] = [];
page.on('requestservedfromcache', (r) => {
return cached.push(r.url().split('/').pop()!);
});
await page.goto(server.PREFIX + '/cached/one-style.html');
expect(cached).toEqual([]);
@ -485,18 +508,20 @@ describe('network', function () {
it('Page.Events.Response', async () => {
const { page, server } = getTestState();
const responses = [];
page.on('response', (response) => responses.push(response));
await page.goto(server.EMPTY_PAGE);
const responses: HTTPResponse[] = [];
page.on('response', (response) => {
return responses.push(response);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE);
expect(responses[0].status()).toBe(200);
expect(responses[0].ok()).toBe(true);
expect(responses[0].request()).toBeTruthy();
const remoteAddress = responses[0].remoteAddress();
expect(responses[0]!.url()).toBe(server.EMPTY_PAGE);
expect(responses[0]!.status()).toBe(200);
expect(responses[0]!.ok()).toBe(true);
expect(responses[0]!.request()).toBeTruthy();
const remoteAddress = responses[0]!.remoteAddress();
// Either IPv6 or IPv4, depending on environment.
expect(
remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1'
remoteAddress.ip!.includes('::1') || remoteAddress.ip === '127.0.0.1'
).toBe(true);
expect(remoteAddress.port).toBe(server.PORT);
});
@ -512,61 +537,73 @@ describe('network', function () {
request.continue();
}
});
const failedRequests = [];
page.on('requestfailed', (request) => failedRequests.push(request));
const failedRequests: HTTPRequest[] = [];
page.on('requestfailed', (request) => {
return failedRequests.push(request);
});
await page.goto(server.PREFIX + '/one-style.html');
expect(failedRequests.length).toBe(1);
expect(failedRequests[0].url()).toContain('one-style.css');
expect(failedRequests[0].response()).toBe(null);
expect(failedRequests[0].resourceType()).toBe('stylesheet');
expect(failedRequests[0]!.url()).toContain('one-style.css');
expect(failedRequests[0]!.response()).toBe(null);
expect(failedRequests[0]!.resourceType()).toBe('stylesheet');
if (isChrome) {
expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED');
expect(failedRequests[0]!.failure()!.errorText).toBe('net::ERR_FAILED');
} 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 () => {
const { page, server } = getTestState();
const requests = [];
page.on('requestfinished', (request) => requests.push(request));
await page.goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on('requestfinished', (request) => {
return requests.push(request);
});
(await page.goto(server.EMPTY_PAGE))!;
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
expect(requests[0].response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
expect(requests[0]!.response()).toBeTruthy();
expect(requests[0]!.frame() === page.mainFrame()).toBe(true);
expect(requests[0]!.frame()!.url()).toBe(server.EMPTY_PAGE);
});
it('should fire events in proper order', async () => {
const { page, server } = getTestState();
const events = [];
page.on('request', () => events.push('request'));
page.on('response', () => events.push('response'));
page.on('requestfinished', () => events.push('requestfinished'));
await page.goto(server.EMPTY_PAGE);
const events: string[] = [];
page.on('request', () => {
return events.push('request');
});
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']);
});
it('should support redirects', async () => {
const { page, server } = getTestState();
const events = [];
page.on('request', (request) =>
events.push(`${request.method()} ${request.url()}`)
);
page.on('response', (response) =>
events.push(`${response.status()} ${response.url()}`)
);
page.on('requestfinished', (request) =>
events.push(`DONE ${request.url()}`)
);
page.on('requestfailed', (request) =>
events.push(`FAIL ${request.url()}`)
);
const events: string[] = [];
page.on('request', (request) => {
return events.push(`${request.method()} ${request.url()}`);
});
page.on('response', (response) => {
return events.push(`${response.status()} ${response.url()}`);
});
page.on('requestfinished', (request) => {
return events.push(`DONE ${request.url()}`);
});
page.on('requestfailed', (request) => {
return events.push(`FAIL ${request.url()}`);
});
server.setRedirect('/foo.html', '/empty.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([
`GET ${FOO_URL}`,
`302 ${FOO_URL}`,
@ -579,8 +616,8 @@ describe('network', function () {
// Check redirect chain
const redirectChain = response.request().redirectChain();
expect(redirectChain.length).toBe(1);
expect(redirectChain[0].url()).toContain('/foo.html');
expect(redirectChain[0].response().remoteAddress().port).toBe(
expect(redirectChain[0]!.url()).toContain('/foo.html');
expect(redirectChain[0]!.response()!.remoteAddress().port).toBe(
server.PORT
);
});
@ -591,9 +628,9 @@ describe('network', function () {
const { page, server } = getTestState();
const requests = new Map();
page.on('request', (request) =>
requests.set(request.url().split('/').pop(), request)
);
page.on('request', (request) => {
return requests.set(request.url().split('/').pop(), request);
});
server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect');
expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
@ -622,10 +659,12 @@ describe('network', function () {
itFailsFirefox('should work when navigating to image', async () => {
const { page, server } = getTestState();
const requests = [];
page.on('request', (request) => requests.push(request));
await page.goto(server.PREFIX + '/pptr.png');
expect(requests[0].isNavigationRequest()).toBe(true);
const requests: HTTPRequest[] = [];
page.on('request', (request) => {
return requests.push(request);
});
(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 () => {
const { page } = getTestState();
let error = null;
let error!: Error;
try {
// @ts-expect-error purposeful bad input
await page.setExtraHTTPHeaders({ foo: 1 });
} catch (error_) {
error = error_;
error = error_ as Error;
}
expect(error.message).toBe(
'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');
let response;
try {
response = await page.goto(server.EMPTY_PAGE);
response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(401);
} catch (error) {
// 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;
}
}
@ -677,7 +720,7 @@ describe('network', function () {
username: 'user',
password: 'pass',
});
response = await page.reload();
response = (await page.reload())!;
expect(response.status()).toBe(200);
});
it('should fail if wrong credentials', async () => {
@ -689,7 +732,7 @@ describe('network', function () {
username: 'foo',
password: 'bar',
});
const response = await page.goto(server.EMPTY_PAGE);
const response = (await page.goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(401);
});
it('should allow disable authentication', async () => {
@ -701,16 +744,25 @@ describe('network', function () {
username: 'user3',
password: 'pass3',
});
let response = await page.goto(server.EMPTY_PAGE);
let response = (await page.goto(server.EMPTY_PAGE))!;
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.
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);
} catch (error) {
// 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;
}
}
@ -727,7 +779,9 @@ describe('network', function () {
});
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.
await page.goto(server.PREFIX + '/cached/one-style.html');
@ -745,27 +799,29 @@ describe('network', function () {
const { page, server } = getTestState();
const setCookieString = 'foo=bar';
server.setRoute('/empty.html', (req, res) => {
server.setRoute('/empty.html', (_req, res) => {
res.setHeader('set-cookie', setCookieString);
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);
});
it('Same-origin set-cookie subresource', async () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
(await page.goto(server.EMPTY_PAGE))!;
const setCookieString = 'foo=bar';
server.setRoute('/foo', (req, res) => {
server.setRoute('/foo', (_req, res) => {
res.setHeader('set-cookie', setCookieString);
res.end('hello world');
});
const responsePromise = new Promise<HTTPResponse>((resolve) =>
page.on('response', (response) => resolve(response))
);
const responsePromise = new Promise<HTTPResponse>((resolve) => {
return page.on('response', (response) => {
return resolve(response);
});
});
page.evaluate(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/foo');
@ -789,7 +845,7 @@ describe('network', function () {
await page.goto(httpsServer.PREFIX + '/empty.html');
const setCookieString = 'hello=world';
httpsServer.setRoute('/setcookie.html', (req, res) => {
httpsServer.setRoute('/setcookie.html', (_req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('set-cookie', setCookieString);
res.end();

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,12 +17,14 @@
import fs from 'fs';
import path from 'path';
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 () {
let outputFile;
let browser;
let page;
let outputFile!: string;
let browser!: Browser;
let page!: Page;
/* we manually manage the browser here as we want a new browser for each
* individual test, which isn't the default behaviour of getTestState()
@ -37,11 +39,8 @@ describeChromeOnly('Tracing', function () {
afterEach(async () => {
await browser.close();
browser = null;
page = null;
if (fs.existsSync(outputFile)) {
fs.unlinkSync(outputFile);
outputFile = null;
}
});
it('should output a trace', async () => {
@ -93,10 +92,10 @@ describeChromeOnly('Tracing', function () {
it('should throw if tracing on two pages', async () => {
await page.tracing.start({ path: outputFile });
const newPage = await browser.newPage();
let error = null;
await newPage.tracing
.start({ path: outputFile })
.catch((error_) => (error = error_));
let error!: Error;
await newPage.tracing.start({ path: outputFile }).catch((error_) => {
return (error = error_);
});
await newPage.close();
expect(error).toBeTruthy();
await page.tracing.stop();
@ -106,7 +105,7 @@ describeChromeOnly('Tracing', function () {
await page.tracing.start({ screenshots: true, path: outputFile });
await page.goto(server.PREFIX + '/grid.html');
const trace = await page.tracing.stop();
const trace = (await page.tracing.stop())!;
const buf = fs.readFileSync(outputFile);
expect(trace.toString()).toEqual(buf.toString());
});
@ -138,18 +137,18 @@ describeChromeOnly('Tracing', function () {
await page.tracing.start({ screenshots: true });
await page.goto(server.PREFIX + '/grid.html');
const trace = await page.tracing.stop();
const trace = (await page.tracing.stop())!;
expect(trace.toString()).toContain('screenshot');
});
it('should properly fail if readProtocolStream errors out', async () => {
await page.tracing.start({ path: __dirname });
let error: Error = null;
let error!: Error;
try {
await page.tracing.stop();
} catch (error_) {
error = error_;
error = error_ as Error;
}
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.
*/
import utils from './utils.js';
import sinon from 'sinon';
import expect from 'expect';
import sinon from 'sinon';
import { isErrorLike } from '../../lib/cjs/puppeteer/common/util.js';
import {
getTestState,
itFailsFirefox,
setupTestBrowserHooks,
setupTestPageAndContextHooks,
itFailsFirefox,
} from './mocha-utils'; // eslint-disable-line import/extensions
} from './mocha-utils.js';
import { attachFrame, detachFrame } from './utils.js';
describe('waittask specs', function () {
setupTestBrowserHooks();
@ -33,13 +34,17 @@ describe('waittask specs', function () {
* tests. Until we remove this method we still want to ensure we don't break
* it.
*/
beforeEach(() => sinon.stub(console, 'warn').callsFake(() => {}));
beforeEach(() => {
return sinon.stub(console, 'warn').callsFake(() => {});
});
it('should wait for selector', async () => {
const { page, server } = getTestState();
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);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
@ -51,7 +56,9 @@ describe('waittask specs', function () {
const { page, server } = getTestState();
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);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
@ -74,8 +81,10 @@ describe('waittask specs', function () {
const { page } = getTestState();
await page.setContent(`<div>some text</div>`);
let error = null;
await page.waitFor('/html/body/div').catch((error_) => (error = error_));
let error!: Error;
await page.waitFor('/html/body/div').catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy();
});
it('should timeout', async () => {
@ -98,28 +107,31 @@ describe('waittask specs', function () {
const { page } = getTestState();
await Promise.all([
page.waitFor(() => window.innerWidth < 100),
page.waitFor(() => {
return window.innerWidth < 100;
}),
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 () => {
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 () => {
const { page } = getTestState();
await page.waitFor(() => true);
await page.waitFor(() => {
return true;
});
const consoleWarnStub = console.warn as sinon.SinonSpy;
@ -138,15 +150,19 @@ describe('waittask specs', function () {
const { page } = getTestState();
const watchdog = page.waitForFunction('window.__FOO === 1');
await page.evaluate(() => (globalThis.__FOO = 1));
await page.evaluate(() => {
return ((globalThis as any).__FOO = 1);
});
await watchdog;
});
it('should work when resolved right before execution context disposal', async () => {
const { page } = getTestState();
await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true));
await page.evaluateOnNewDocument(() => {
return ((globalThis as any).__RELOADED = true);
});
await page.waitForFunction(() => {
if (!globalThis.__RELOADED) {
if (!(globalThis as any).__RELOADED) {
window.location.reload();
}
return true;
@ -159,13 +175,24 @@ describe('waittask specs', function () {
const startTime = Date.now();
const polling = 100;
const watchdog = page
.waitForFunction(() => globalThis.__FOO === 'hit', { polling })
.then(() => (success = true));
await page.evaluate(() => (globalThis.__FOO = 'hit'));
.waitForFunction(
() => {
return (globalThis as any).__FOO === 'hit';
},
{
polling,
}
)
.then(() => {
return (success = true);
});
await page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
});
expect(success).toBe(false);
await page.evaluate(() =>
document.body.appendChild(document.createElement('div'))
);
await page.evaluate(() => {
return document.body.appendChild(document.createElement('div'));
});
await watchdog;
expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
});
@ -175,13 +202,24 @@ describe('waittask specs', function () {
const startTime = Date.now();
const polling = 100;
const watchdog = page
.waitForFunction(async () => globalThis.__FOO === 'hit', { polling })
.then(() => (success = true));
await page.evaluate(async () => (globalThis.__FOO = 'hit'));
.waitForFunction(
async () => {
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);
await page.evaluate(async () =>
document.body.appendChild(document.createElement('div'))
);
await page.evaluate(async () => {
return document.body.appendChild(document.createElement('div'));
});
await watchdog;
expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
});
@ -190,15 +228,24 @@ describe('waittask specs', function () {
let success = false;
const watchdog = page
.waitForFunction(() => globalThis.__FOO === 'hit', {
.waitForFunction(
() => {
return (globalThis as any).__FOO === 'hit';
},
{
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);
await page.evaluate(() =>
document.body.appendChild(document.createElement('div'))
);
await page.evaluate(() => {
return document.body.appendChild(document.createElement('div'));
});
await watchdog;
});
it('should poll on mutation async', async () => {
@ -206,36 +253,56 @@ describe('waittask specs', function () {
let success = false;
const watchdog = page
.waitForFunction(async () => globalThis.__FOO === 'hit', {
.waitForFunction(
async () => {
return (globalThis as any).__FOO === 'hit';
},
{
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);
await page.evaluate(async () =>
document.body.appendChild(document.createElement('div'))
);
await page.evaluate(async () => {
return document.body.appendChild(document.createElement('div'));
});
await watchdog;
});
it('should poll on raf', async () => {
const { page } = getTestState();
const watchdog = page.waitForFunction(() => globalThis.__FOO === 'hit', {
const watchdog = page.waitForFunction(
() => {
return (globalThis as any).__FOO === 'hit';
},
{
polling: 'raf',
}
);
await page.evaluate(() => {
return ((globalThis as any).__FOO = 'hit');
});
await page.evaluate(() => (globalThis.__FOO = 'hit'));
await watchdog;
});
it('should poll on raf async', async () => {
const { page } = getTestState();
const watchdog = page.waitForFunction(
async () => globalThis.__FOO === 'hit',
async () => {
return (globalThis as any).__FOO === 'hit';
},
{
polling: 'raf',
}
);
await page.evaluate(async () => (globalThis.__FOO = 'hit'));
await page.evaluate(async () => {
return ((globalThis as any).__FOO = 'hit');
});
await watchdog;
});
itFailsFirefox('should work with strict CSP policy', async () => {
@ -243,100 +310,148 @@ describe('waittask specs', function () {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.EMPTY_PAGE);
let error = null;
let error!: Error;
await Promise.all([
page
.waitForFunction(() => globalThis.__FOO === 'hit', { polling: 'raf' })
.catch((error_) => (error = error_)),
page.evaluate(() => (globalThis.__FOO = 'hit')),
.waitForFunction(
() => {
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 () => {
const { page } = getTestState();
let error = null;
let error!: Error;
try {
await page.waitForFunction(() => !!document.body, {
await page.waitForFunction(
() => {
return !!document.body;
},
{
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 () => {
const { page } = getTestState();
let error = null;
let error!: Error;
try {
await page.waitForFunction(() => !!document.body, { polling: -10 });
await page.waitForFunction(
() => {
return !!document.body;
},
{ polling: -10 }
);
} 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 () => {
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 () => {
const { page } = getTestState();
expect(await page.waitForFunction(() => window)).toBeTruthy();
expect(
await page.waitForFunction(() => {
return window;
})
).toBeTruthy();
});
it('should accept ElementHandle arguments', async () => {
const { page } = getTestState();
await page.setContent('<div></div>');
const div = await page.$('div');
const div = (await page.$('div'))!;
let resolved = false;
const waitForFunction = page
.waitForFunction(
(element) => element.localName === 'div' && !element.parentElement,
(element: Element) => {
return element.localName === 'div' && !element.parentElement;
},
{},
div
)
.then(() => (resolved = true));
.then(() => {
return (resolved = true);
});
expect(resolved).toBe(false);
await page.evaluate((element: HTMLElement) => element.remove(), div);
await page.evaluate((element: HTMLElement) => {
return element.remove();
}, div);
await waitForFunction;
});
it('should respect timeout', async () => {
const { page, puppeteer } = getTestState();
let error = null;
await page
.waitForFunction('false', { timeout: 10 })
.catch((error_) => (error = error_));
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for function failed: timeout');
let error!: Error;
await page.waitForFunction('false', { timeout: 10 }).catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error?.message).toContain('waiting for function failed: timeout');
});
it('should respect default timeout', async () => {
const { page, puppeteer } = getTestState();
page.setDefaultTimeout(1);
let error = null;
await page.waitForFunction('false').catch((error_) => (error = error_));
let error!: Error;
await page.waitForFunction('false').catch((error_) => {
return (error = error_);
});
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 () => {
const { page } = getTestState();
const watchdog = page.waitForFunction(
() => {
globalThis.__counter = (globalThis.__counter || 0) + 1;
return globalThis.__injected;
(globalThis as any).__counter =
((globalThis as any).__counter || 0) + 1;
return (globalThis as any).__injected;
},
{ timeout: 0, polling: 10 }
);
await page.waitForFunction(() => globalThis.__counter > 10);
await page.evaluate(() => (globalThis.__injected = true));
await page.waitForFunction(() => {
return (globalThis as any).__counter > 10;
});
await page.evaluate(() => {
return ((globalThis as any).__injected = true);
});
await watchdog;
});
it('should survive cross-process navigation', async () => {
@ -345,24 +460,32 @@ describe('waittask specs', function () {
let fooFound = false;
const waitForFunction = page
.waitForFunction('globalThis.__FOO === 1')
.then(() => (fooFound = true));
.then(() => {
return (fooFound = true);
});
await page.goto(server.EMPTY_PAGE);
expect(fooFound).toBe(false);
await page.reload();
expect(fooFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
expect(fooFound).toBe(false);
await page.evaluate(() => (globalThis.__FOO = 1));
await page.evaluate(() => {
return ((globalThis as any).__FOO = 1);
});
await waitForFunction;
expect(fooFound).toBe(true);
});
it('should survive navigations', async () => {
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.PREFIX + '/consolelog.html');
await page.evaluate(() => (globalThis.__done = true));
await page.evaluate(() => {
return ((globalThis as any).__done = true);
});
await watchdog;
});
});
@ -401,8 +524,9 @@ describe('waittask specs', function () {
});
describe('Frame.waitForSelector', function () {
const addElement = (tag) =>
document.body.appendChild(document.createElement(tag));
const addElement = (tag: string) => {
return document.body.appendChild(document.createElement(tag));
};
it('should immediately resolve promise if node exists', async () => {
const { page, server } = getTestState();
@ -417,13 +541,18 @@ describe('waittask specs', function () {
itFailsFirefox('should work with removed MutationObserver', async () => {
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([
page.waitForSelector('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`),
]);
expect(
await page.evaluate((x: HTMLElement) => x.textContent, handle)
await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, handle)
).toBe('anything');
});
@ -435,10 +564,8 @@ describe('waittask specs', function () {
const watchdog = frame.waitForSelector('div');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
const eHandle = await watchdog;
const tagName = await eHandle
.getProperty('tagName')
.then((e) => e.jsonValue());
const eHandle = (await watchdog)!;
const tagName = await (await eHandle.getProperty('tagName')).jsonValue();
expect(tagName).toBe('DIV');
});
@ -448,10 +575,10 @@ describe('waittask specs', function () {
await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div');
await page.evaluate(addElement, 'span');
await page.evaluate(
() =>
(document.querySelector('span').innerHTML = '<h3><div></div></h3>')
);
await page.evaluate(() => {
return (document.querySelector('span')!.innerHTML =
'<h3><div></div></h3>');
});
await watchdog;
});
@ -461,43 +588,43 @@ describe('waittask specs', function () {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1];
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1]!;
const watchdog = page.waitForSelector('div');
await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div');
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 () => {
const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
await attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]!;
const frame2 = page.frames()[2]!;
const waitForSelectorPromise = frame2.waitForSelector('div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise;
expect(eHandle.executionContext().frame()).toBe(frame2);
expect(eHandle?.executionContext().frame()).toBe(frame2);
});
itFailsFirefox('should throw when frame is detached', async () => {
const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
const waitPromise = frame
.waitForSelector('.box')
.catch((error) => (waitError = error));
await utils.detachFrame(page, 'frame1');
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]!;
let waitError: Error | undefined;
const waitPromise = frame.waitForSelector('.box').catch((error) => {
return (waitError = error);
});
await detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain(
expect(waitError?.message).toContain(
'waitForFunction failed: frame got detached.'
);
});
@ -505,9 +632,9 @@ describe('waittask specs', function () {
const { page, server } = getTestState();
let boxFound = false;
const waitForSelector = page
.waitForSelector('.box')
.then(() => (boxFound = true));
const waitForSelector = page.waitForSelector('.box').then(() => {
return (boxFound = true);
});
await page.goto(server.EMPTY_PAGE);
expect(boxFound).toBe(false);
await page.reload();
@ -522,18 +649,22 @@ describe('waittask specs', function () {
let divFound = false;
const waitForSelector = page
.waitForSelector('div', { visible: true })
.then(() => (divFound = true));
.then(() => {
return (divFound = true);
});
await page.setContent(
`<div style='display: none; visibility: hidden;'>1</div>`
);
expect(divFound).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.removeProperty('display')
);
await page.evaluate(() => {
return document.querySelector('div')?.style.removeProperty('display');
});
expect(divFound).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.removeProperty('visibility')
);
await page.evaluate(() => {
return document
.querySelector('div')
?.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true);
expect(divFound).toBe(true);
});
@ -543,18 +674,22 @@ describe('waittask specs', function () {
let divVisible = false;
const waitForSelector = page
.waitForSelector('div#inner', { visible: true })
.then(() => (divVisible = true));
.then(() => {
return (divVisible = true);
});
await page.setContent(
`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`
);
expect(divVisible).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.removeProperty('display')
);
await page.evaluate(() => {
return document.querySelector('div')?.style.removeProperty('display');
});
expect(divVisible).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.removeProperty('visibility')
);
await page.evaluate(() => {
return document
.querySelector('div')
?.style.removeProperty('visibility');
});
expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true);
});
@ -565,12 +700,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page
.waitForSelector('div', { hidden: true })
.then(() => (divHidden = true));
.then(() => {
return (divHidden = true);
});
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.setProperty('visibility', 'hidden')
);
await page.evaluate(() => {
return document
.querySelector('div')
?.style.setProperty('visibility', 'hidden');
});
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
@ -581,12 +720,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page
.waitForSelector('div', { hidden: true })
.then(() => (divHidden = true));
.then(() => {
return (divHidden = true);
});
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.setProperty('display', 'none')
);
await page.evaluate(() => {
return document
.querySelector('div')
?.style.setProperty('display', 'none');
});
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
@ -597,10 +740,14 @@ describe('waittask specs', function () {
let divRemoved = false;
const waitForSelector = page
.waitForSelector('div', { hidden: true })
.then(() => (divRemoved = true));
.then(() => {
return (divRemoved = true);
});
await page.waitForSelector('div'); // do a round trip
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(divRemoved).toBe(true);
});
@ -615,26 +762,27 @@ describe('waittask specs', function () {
it('should respect timeout', async () => {
const { page, puppeteer } = getTestState();
let error = null;
await page
.waitForSelector('div', { timeout: 10 })
.catch((error_) => (error = error_));
expect(error).toBeTruthy();
expect(error.message).toContain(
let error!: Error;
await page.waitForSelector('div', { timeout: 10 }).catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error?.message).toContain(
'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 () => {
const { page } = getTestState();
await page.setContent(`<div></div>`);
let error = null;
let error!: Error;
await page
.waitForSelector('div', { hidden: true, timeout: 10 })
.catch((error_) => (error = error_));
.catch((error_) => {
return (error = error_);
});
expect(error).toBeTruthy();
expect(error.message).toContain(
expect(error?.message).toContain(
'waiting for selector `div` to be hidden failed: timeout'
);
});
@ -643,14 +791,14 @@ describe('waittask specs', function () {
const { page } = getTestState();
let divFound = false;
const waitForSelector = page
.waitForSelector('.zombo')
.then(() => (divFound = true));
const waitForSelector = page.waitForSelector('.zombo').then(() => {
return (divFound = true);
});
await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false);
await page.evaluate(
() => (document.querySelector('div').className = 'zombo')
);
await page.evaluate(() => {
return (document.querySelector('div')!.className = 'zombo');
});
expect(await waitForSelector).toBe(true);
});
it('should return the element handle', async () => {
@ -659,28 +807,28 @@ describe('waittask specs', function () {
const waitForSelector = page.waitForSelector('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(
await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForSelector
)
await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, await waitForSelector)
).toBe('anything');
});
it('should have correct stack trace for timeout', async () => {
const { page } = getTestState();
let error;
await page
.waitForSelector('.zombo', { timeout: 10 })
.catch((error_) => (error = error_));
expect(error.stack).toContain('waiting for selector `.zombo` failed');
let error!: Error;
await page.waitForSelector('.zombo', { timeout: 10 }).catch((error_) => {
return (error = error_);
});
expect(error?.stack).toContain('waiting for selector `.zombo` failed');
// 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 () {
const addElement = (tag) =>
document.body.appendChild(document.createElement(tag));
const addElement = (tag: string) => {
return document.body.appendChild(document.createElement(tag));
};
it('should support some fancy xpath', async () => {
const { page } = getTestState();
@ -690,51 +838,51 @@ describe('waittask specs', function () {
'//p[normalize-space(.)="hello world"]'
);
expect(
await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, await waitForXPath)
).toBe('hello world ');
});
it('should respect timeout', async () => {
const { page, puppeteer } = getTestState();
let error = null;
await page
.waitForXPath('//div', { timeout: 10 })
.catch((error_) => (error = error_));
expect(error).toBeTruthy();
expect(error.message).toContain(
let error!: Error;
await page.waitForXPath('//div', { timeout: 10 }).catch((error_) => {
return (error = error_);
});
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
expect(error?.message).toContain(
'waiting for XPath `//div` failed: timeout'
);
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
});
itFailsFirefox('should run in specified frame', async () => {
const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
await attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]!;
const frame2 = page.frames()[2]!;
const waitForXPathPromise = frame2.waitForXPath('//div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise;
expect(eHandle.executionContext().frame()).toBe(frame2);
expect(eHandle?.executionContext().frame()).toBe(frame2);
});
itFailsFirefox('should throw when frame is detached', async () => {
const { page, server } = getTestState();
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]!;
let waitError: Error | undefined;
const waitPromise = frame
.waitForXPath('//*[@class="box"]')
.catch((error) => (waitError = error));
await utils.detachFrame(page, 'frame1');
.catch((error) => {
return (waitError = error);
});
await detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain(
expect(waitError?.message).toContain(
'waitForFunction failed: frame got detached.'
);
});
@ -745,12 +893,16 @@ describe('waittask specs', function () {
await page.setContent(`<div style='display: block;'></div>`);
const waitForXPath = page
.waitForXPath('//div', { hidden: true })
.then(() => (divHidden = true));
.then(() => {
return (divHidden = true);
});
await page.waitForXPath('//div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() =>
document.querySelector('div').style.setProperty('display', 'none')
);
await page.evaluate(() => {
return document
.querySelector('div')
?.style.setProperty('display', 'none');
});
expect(await waitForXPath).toBe(true);
expect(divHidden).toBe(true);
});
@ -760,10 +912,9 @@ describe('waittask specs', function () {
const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(
await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, await waitForXPath)
).toBe('anything');
});
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>`);
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 */
);
});
@ -781,10 +932,9 @@ describe('waittask specs', function () {
await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForXPath('/html/body/div');
expect(
await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
await page.evaluate((x: HTMLElement) => {
return x.textContent;
}, await waitForXPath)
).toBe('some text');
});
});

View File

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

View File

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

View File

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

View File

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