feat(types): improve page.evaluate types (#6193)

This commit is contained in:
Jack Franklin 2020-07-10 11:52:13 +01:00 committed by GitHub
parent 31309b0e20
commit 9b3005c105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 216 additions and 106 deletions

View File

@ -8,5 +8,5 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
export declare type EvaluateFn<T = any> = string | ((arg1: T, ...args: any[]) => any); export declare type EvaluateFn<T = unknown> = string | ((arg1: T, ...args: unknown[]) => unknown);
``` ```

View File

@ -8,5 +8,5 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
export declare type EvaluateFnReturnType<T extends EvaluateFn> = T extends (...args: any[]) => infer R ? R : unknown; export declare type EvaluateFnReturnType<T extends EvaluateFn> = T extends (...args: unknown[]) => infer R ? R : unknown;
``` ```

View File

@ -7,19 +7,19 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
evaluate<ReturnType extends any>(pageFunction: Function | string, ...args: unknown[]): Promise<ReturnType>; evaluate<T extends EvaluateFn>(pageFunction: T, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| pageFunction | Function \| string | a function that is run within the frame | | pageFunction | T | a function that is run within the frame |
| args | unknown\[\] | arguments to be passed to the pageFunction | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | arguments to be passed to the pageFunction |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[UnwrapPromiseLike](./puppeteer.unwrappromiselike.md)<!-- -->&lt;[EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)<!-- -->&lt;T&gt;&gt;&gt;
## Remarks ## Remarks

View File

@ -9,7 +9,7 @@ This method passes this handle as the first argument to `pageFunction`<!-- -->.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
evaluate<T extends EvaluateFn>(pageFunction: T | string, ...args: SerializableOrJSHandle[]): Promise<EvaluateFnReturnType<T>>; evaluate<T extends EvaluateFn>(pageFunction: T | string, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
``` ```
## Parameters ## Parameters
@ -21,7 +21,7 @@ evaluate<T extends EvaluateFn>(pageFunction: T | string, ...args: SerializableOr
<b>Returns:</b> <b>Returns:</b>
Promise&lt;[EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)<!-- -->&lt;T&gt;&gt; Promise&lt;[UnwrapPromiseLike](./puppeteer.unwrappromiselike.md)<!-- -->&lt;[EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)<!-- -->&lt;T&gt;&gt;&gt;
## Example ## Example

View File

@ -109,5 +109,6 @@
| [Serializable](./puppeteer.serializable.md) | | | [Serializable](./puppeteer.serializable.md) | |
| [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md) | | | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md) | |
| [UnwrapElementHandle](./puppeteer.unwrapelementhandle.md) | Unwraps a DOM element out of an ElementHandle instance | | [UnwrapElementHandle](./puppeteer.unwrapelementhandle.md) | Unwraps a DOM element out of an ElementHandle instance |
| [UnwrapPromiseLike](./puppeteer.unwrappromiselike.md) | |
| [WrapElementHandle](./puppeteer.wrapelementhandle.md) | Wraps a DOM element into an ElementHandle instance | | [WrapElementHandle](./puppeteer.wrapelementhandle.md) | Wraps a DOM element into an ElementHandle instance |

View File

@ -7,19 +7,19 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
evaluate<ReturnType extends any>(pageFunction: Function | string, ...args: unknown[]): Promise<ReturnType>; evaluate<T extends EvaluateFn>(pageFunction: T, ...args: SerializableOrJSHandle[]): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>>;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| pageFunction | Function \| string | a function that is run within the page | | pageFunction | T | a function that is run within the page |
| args | unknown\[\] | arguments to be passed to the pageFunction | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | arguments to be passed to the pageFunction |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[UnwrapPromiseLike](./puppeteer.unwrappromiselike.md)<!-- -->&lt;[EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)<!-- -->&lt;T&gt;&gt;&gt;
the return value of `pageFunction`<!-- -->. the return value of `pageFunction`<!-- -->.
@ -47,6 +47,12 @@ You can pass a string instead of a function (although functions are recommended
``` ```
const aHandle = await page.evaluate('1 + 2'); const aHandle = await page.evaluate('1 + 2');
```
To get the best TypeScript experience, you should pass in as the generic the type of `pageFunction`<!-- -->:
```
const aHandle = await page.evaluate<() => number>(() => 2);
``` ```
## Example 3 ## Example 3

View File

@ -8,5 +8,5 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
export declare type Serializable = number | string | boolean | null | JSONArray | JSONObject; export declare type Serializable = number | string | boolean | null | BigInt | JSONArray | JSONObject;
``` ```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [UnwrapPromiseLike](./puppeteer.unwrappromiselike.md)
## UnwrapPromiseLike type
<b>Signature:</b>
```typescript
export declare type UnwrapPromiseLike<T> = T extends PromiseLike<infer U> ? U : T;
```

View File

@ -28,6 +28,9 @@ import {
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes'; } from './EvalTypes';
import { isNode } from '../environment'; import { isNode } from '../environment';
@ -118,12 +121,15 @@ export class DOMWorld {
return context.evaluateHandle(pageFunction, ...args); return context.evaluateHandle(pageFunction, ...args);
} }
async evaluate<ReturnType extends any>( async evaluate<T extends EvaluateFn>(
pageFunction: Function | string, pageFunction: T,
...args: unknown[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
const context = await this.executionContext(); const context = await this.executionContext();
return context.evaluate<ReturnType>(pageFunction, ...args); return context.evaluate<UnwrapPromiseLike<EvaluateFnReturnType<T>>>(
pageFunction,
...args
);
} }
async $(selector: string): Promise<ElementHandle | null> { async $(selector: string): Promise<ElementHandle | null> {
@ -206,7 +212,7 @@ export class DOMWorld {
} = options; } = options;
// We rely upon the fact that document.open() will reset frame lifecycle with "init" // We rely upon the fact that document.open() will reset frame lifecycle with "init"
// lifecycle event. @see https://crrev.com/608658 // lifecycle event. @see https://crrev.com/608658
await this.evaluate((html) => { await this.evaluate<(x: string) => void>((html) => {
document.open(); document.open();
document.write(html); document.write(html);
document.close(); document.close();

View File

@ -19,12 +19,17 @@ import { JSHandle, ElementHandle } from './JSHandle';
/** /**
* @public * @public
*/ */
export type EvaluateFn<T = any> = string | ((arg1: T, ...args: any[]) => any); export type EvaluateFn<T = unknown> =
| string
| ((arg1: T, ...args: unknown[]) => unknown);
export type UnwrapPromiseLike<T> = T extends PromiseLike<infer U> ? U : T;
/** /**
* @public * @public
*/ */
export type EvaluateFnReturnType<T extends EvaluateFn> = T extends ( export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (
...args: any[] ...args: unknown[]
) => infer R ) => infer R
? R ? R
: unknown; : unknown;
@ -42,6 +47,7 @@ export type Serializable =
| string | string
| boolean | boolean
| null | null
| BigInt
| JSONArray | JSONArray
| JSONObject; | JSONObject;

View File

@ -32,6 +32,9 @@ import {
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes'; } from './EvalTypes';
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
@ -684,11 +687,11 @@ export class Frame {
* @param pageFunction - a function that is run within the frame * @param pageFunction - a function that is run within the frame
* @param args - arguments to be passed to the pageFunction * @param args - arguments to be passed to the pageFunction
*/ */
async evaluate<ReturnType extends any>( async evaluate<T extends EvaluateFn>(
pageFunction: Function | string, pageFunction: T,
...args: unknown[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
return this._mainWorld.evaluate<ReturnType>(pageFunction, ...args); return this._mainWorld.evaluate<T>(pageFunction, ...args);
} }
/** /**

View File

@ -29,6 +29,7 @@ import {
EvaluateFnReturnType, EvaluateFnReturnType,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
UnwrapPromiseLike,
} from './EvalTypes'; } from './EvalTypes';
export interface BoxModel { export interface BoxModel {
@ -153,12 +154,10 @@ export class JSHandle {
async evaluate<T extends EvaluateFn>( async evaluate<T extends EvaluateFn>(
pageFunction: T | string, pageFunction: T | string,
...args: SerializableOrJSHandle[] ...args: SerializableOrJSHandle[]
): Promise<EvaluateFnReturnType<T>> { ): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
return await this.executionContext().evaluate<EvaluateFnReturnType<T>>( return await this.executionContext().evaluate<
pageFunction, UnwrapPromiseLike<EvaluateFnReturnType<T>>
this, >(pageFunction, this, ...args);
...args
);
} }
/** /**
@ -620,7 +619,9 @@ export class ElementHandle<
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element. * Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
*/ */
async focus(): Promise<void> { async focus(): Promise<void> {
await this.evaluate((element) => element.focus()); await this.evaluate<(element: HTMLElement) => void>((element) =>
element.focus()
);
} }
/** /**

View File

@ -45,6 +45,9 @@ import {
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes'; } from './EvalTypes';
const writeFileAsync = promisify(fs.writeFile); const writeFileAsync = promisify(fs.writeFile);
@ -1516,6 +1519,13 @@ export class Page extends EventEmitter {
* const aHandle = await page.evaluate('1 + 2'); * const aHandle = await page.evaluate('1 + 2');
* ``` * ```
* *
* To get the best TypeScript experience, you should pass in as the
* generic the type of `pageFunction`:
*
* ```
* const aHandle = await page.evaluate<() => number>(() => 2);
* ```
*
* @example * @example
* *
* {@link ElementHandle} instances (including {@link JSHandle}s) can be passed * {@link ElementHandle} instances (including {@link JSHandle}s) can be passed
@ -1532,13 +1542,11 @@ export class Page extends EventEmitter {
* *
* @returns the return value of `pageFunction`. * @returns the return value of `pageFunction`.
*/ */
async evaluate<ReturnType extends any>( async evaluate<T extends EvaluateFn>(
pageFunction: Function | string, pageFunction: T,
...args: unknown[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
return this._frameManager return this._frameManager.mainFrame().evaluate<T>(pageFunction, ...args);
.mainFrame()
.evaluate<ReturnType>(pageFunction, ...args);
} }
async evaluateOnNewDocument( async evaluateOnNewDocument(

View File

@ -66,7 +66,10 @@ describe('BrowserContext', function () {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [popupTarget] = await Promise.all([ const [popupTarget] = await Promise.all([
utils.waitEvent(browser, 'targetcreated'), utils.waitEvent(browser, 'targetcreated'),
page.evaluate((url) => window.open(url), server.EMPTY_PAGE), page.evaluate<(url: string) => void>(
(url) => window.open(url),
server.EMPTY_PAGE
),
]); ]);
expect(popupTarget.browserContext()).toBe(context); expect(popupTarget.browserContext()).toBe(context);
await context.close(); await context.close();

View File

@ -389,9 +389,9 @@ describe('Cookie specs', () => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({ name: 'localhost-cookie', value: 'best' }); await page.setCookie({ name: 'localhost-cookie', value: 'best' });
await page.evaluate((src) => { await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill; let fulfill;
const promise = new Promise((x) => (fulfill = x)); const promise = new Promise<void>((x) => (fulfill = x));
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
document.body.appendChild(iframe); document.body.appendChild(iframe);
iframe.onload = fulfill; iframe.onload = fulfill;
@ -454,9 +454,9 @@ describe('Cookie specs', () => {
try { try {
await page.goto(httpsServer.PREFIX + '/grid.html'); await page.goto(httpsServer.PREFIX + '/grid.html');
await page.evaluate((src) => { await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill; let fulfill;
const promise = new Promise((x) => (fulfill = x)); const promise = new Promise<void>((x) => (fulfill = x));
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
document.body.appendChild(iframe); document.body.appendChild(iframe);
iframe.onload = fulfill; iframe.onload = fulfill;

View File

@ -264,7 +264,7 @@ describe('Coverage specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.coverage.startCSSCoverage(); await page.coverage.startCSSCoverage();
await page.evaluate(async (url) => { await page.evaluate<(url: string) => Promise<void>>(async (url) => {
document.body.textContent = 'hello, world'; document.body.textContent = 'hello, world';
const link = document.createElement('link'); const link = document.createElement('link');

View File

@ -67,7 +67,7 @@ describe('ElementHandle specs', function () {
'<div style="width: 100px; height: 100px">hello</div>' '<div style="width: 100px; height: 100px">hello</div>'
); );
const elementHandle = await page.$('div'); const elementHandle = await page.$('div');
await page.evaluate( await page.evaluate<(element: HTMLElement) => void>(
(element) => (element.style.height = '200px'), (element) => (element.style.height = '200px'),
elementHandle elementHandle
); );
@ -84,7 +84,7 @@ describe('ElementHandle specs', function () {
`); `);
const element = await page.$('#therect'); const element = await page.$('#therect');
const pptrBoundingBox = await element.boundingBox(); const pptrBoundingBox = await element.boundingBox();
const webBoundingBox = await page.evaluate((e) => { const webBoundingBox = await page.evaluate((e: HTMLElement) => {
const rect = e.getBoundingClientRect(); const rect = e.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
}, element); }, element);
@ -211,7 +211,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = await page.$('button');
await page.evaluate((button) => button.remove(), button); await page.evaluate((button: HTMLElement) => button.remove(), button);
let error = null; let error = null;
await button.click().catch((error_) => (error = error_)); await button.click().catch((error_) => (error = error_));
expect(error.message).toBe('Node is detached from document'); expect(error.message).toBe('Node is detached from document');
@ -221,7 +221,10 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = await page.$('button');
await page.evaluate((button) => (button.style.display = 'none'), button); await page.evaluate(
(button: HTMLElement) => (button.style.display = 'none'),
button
);
const error = await button.click().catch((error_) => error_); const error = await button.click().catch((error_) => error_);
expect(error.message).toBe( expect(error.message).toBe(
'Node is either not visible or not an HTMLElement' 'Node is either not visible or not an HTMLElement'
@ -233,7 +236,7 @@ describe('ElementHandle specs', function () {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = await page.$('button');
await page.evaluate( await page.evaluate(
(button) => (button.parentElement.style.display = 'none'), (button: HTMLElement) => (button.parentElement.style.display = 'none'),
button button
); );
const error = await button.click().catch((error_) => error_); const error = await button.click().catch((error_) => error_);
@ -295,7 +298,12 @@ describe('ElementHandle specs', function () {
(element, selector) => document.querySelector(`[id="${selector}"]`) (element, selector) => document.querySelector(`[id="${selector}"]`)
); );
const element = await page.$('getById/foo'); const element = await page.$('getById/foo');
expect(await page.evaluate((element) => element.id, element)).toBe('foo'); expect(
await page.evaluate<(element: HTMLElement) => string>(
(element) => element.id,
element
)
).toBe('foo');
// Unregister. // Unregister.
puppeteer.__experimental_unregisterCustomQueryHandler('getById'); puppeteer.__experimental_unregisterCustomQueryHandler('getById');
@ -340,7 +348,10 @@ describe('ElementHandle specs', function () {
const classNames = await Promise.all( const classNames = await Promise.all(
elements.map( elements.map(
async (element) => async (element) =>
await page.evaluate((element) => element.className, element) await page.evaluate<(element: HTMLElement) => string>(
(element) => element.className,
element
)
) )
); );

View File

@ -138,7 +138,7 @@ describe('Emulation', () => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = await page.$('button');
await page.evaluate( await page.evaluate(
(button) => (button.style.marginTop = '200px'), (button: HTMLElement) => (button.style.marginTop = '200px'),
button button
); );
await button.click(); await button.click();

View File

@ -40,7 +40,7 @@ describe('Evaluation specs', function () {
(bigint ? it : xit)('should transfer BigInt', async () => { (bigint ? it : xit)('should transfer BigInt', async () => {
const { page } = getTestState(); const { page } = getTestState();
const result = await page.evaluate((a) => a, BigInt(42)); const result = await page.evaluate((a: BigInt) => a, BigInt(42));
expect(result).toBe(BigInt(42)); expect(result).toBe(BigInt(42));
}); });
it('should transfer NaN', async () => { it('should transfer NaN', async () => {
@ -155,7 +155,11 @@ describe('Evaluation specs', function () {
// Setup inpage callback, which calls Page.evaluate // Setup inpage callback, which calls Page.evaluate
await page.exposeFunction('callController', async function (a, b) { await page.exposeFunction('callController', async function (a, b) {
return await page.evaluate((a, b) => a * b, a, b); return await page.evaluate<(a: number, b: number) => number>(
(a, b) => a * b,
a,
b
);
}); });
const result = await page.evaluate(async function () { const result = await page.evaluate(async function () {
return await globalThis.callController(9, 3); return await globalThis.callController(9, 3);
@ -277,7 +281,7 @@ describe('Evaluation specs', function () {
.jsonValue() .jsonValue()
.catch((error_) => error_.message); .catch((error_) => error_.message);
const error = await page const error = await page
.evaluate<Error>((errorText) => { .evaluate<(errorText: string) => Error>((errorText) => {
throw new Error(errorText); throw new Error(errorText);
}, errorText) }, errorText)
.catch((error_) => error_); .catch((error_) => error_);
@ -306,7 +310,10 @@ describe('Evaluation specs', function () {
await page.setContent('<section>42</section>'); await page.setContent('<section>42</section>');
const element = await page.$('section'); const element = await page.$('section');
const text = await page.evaluate((e) => e.textContent, element); const text = await page.evaluate<(e: HTMLElement) => string>(
(e) => e.textContent,
element
);
expect(text).toBe('42'); expect(text).toBe('42');
}); });
it('should throw if underlying element was disposed', async () => { it('should throw if underlying element was disposed', async () => {
@ -318,7 +325,7 @@ describe('Evaluation specs', function () {
await element.dispose(); await element.dispose();
let error = null; let error = null;
await page await page
.evaluate((e) => e.textContent, element) .evaluate((e: HTMLElement) => e.textContent, element)
.catch((error_) => (error = error_)); .catch((error_) => (error = error_));
expect(error.message).toContain('JSHandle is disposed'); expect(error.message).toContain('JSHandle is disposed');
}); });
@ -331,7 +338,7 @@ describe('Evaluation specs', function () {
const bodyHandle = await page.frames()[1].$('body'); const bodyHandle = await page.frames()[1].$('body');
let error = null; let error = null;
await page await page
.evaluate((body) => body.innerHTML, bodyHandle) .evaluate((body: HTMLElement) => body.innerHTML, bodyHandle)
.catch((error_) => (error = error_)); .catch((error_) => (error = error_));
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain( expect(error.message).toContain(
@ -379,7 +386,7 @@ describe('Evaluation specs', function () {
it('should transfer 100Mb of data from page to node.js', async function () { it('should transfer 100Mb of data from page to node.js', async function () {
const { page } = getTestState(); const { page } = getTestState();
const a = await page.evaluate<any[]>(() => const a = await page.evaluate<() => string>(() =>
Array(100 * 1024 * 1024 + 1).join('a') Array(100 * 1024 * 1024 + 1).join('a')
); );
expect(a.length).toBe(100 * 1024 * 1024); expect(a.length).toBe(100 * 1024 * 1024);

View File

@ -200,7 +200,7 @@ describe('Frame specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/shadow.html'); await page.goto(server.PREFIX + '/shadow.html');
await page.evaluate(async (url) => { await page.evaluate(async (url: string) => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');
frame.src = url; frame.src = url;
document.body.shadowRoot.appendChild(frame); document.body.shadowRoot.appendChild(frame);
@ -213,7 +213,7 @@ describe('Frame specs', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
await page.evaluate((url) => { await page.evaluate((url: string) => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');
frame.name = 'theFrameName'; frame.name = 'theFrameName';
frame.src = url; frame.src = url;

View File

@ -36,7 +36,7 @@ describe('input tests', function () {
await page.goto(server.PREFIX + '/input/fileupload.html'); await page.goto(server.PREFIX + '/input/fileupload.html');
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
const input = await page.$('input'); const input = await page.$('input');
await page.evaluate((e) => { await page.evaluate((e: HTMLElement) => {
globalThis._inputEvents = []; globalThis._inputEvents = [];
e.addEventListener('change', (ev) => e.addEventListener('change', (ev) =>
globalThis._inputEvents.push(ev.type) globalThis._inputEvents.push(ev.type)
@ -46,18 +46,18 @@ describe('input tests', function () {
); );
}, input); }, input);
await input.uploadFile(filePath); await input.uploadFile(filePath);
expect(await page.evaluate((e) => e.files[0].name, input)).toBe( expect(
'file-to-upload.txt' await page.evaluate((e: HTMLInputElement) => e.files[0].name, input)
); ).toBe('file-to-upload.txt');
expect(await page.evaluate((e) => e.files[0].type, input)).toBe( expect(
'text/plain' await page.evaluate((e: HTMLInputElement) => e.files[0].type, input)
); ).toBe('text/plain');
expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([ expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([
'input', 'input',
'change', 'change',
]); ]);
expect( expect(
await page.evaluate((e) => { await page.evaluate((e: HTMLInputElement) => {
const reader = new FileReader(); const reader = new FileReader();
const promise = new Promise((fulfill) => (reader.onload = fulfill)); const promise = new Promise((fulfill) => (reader.onload = fulfill));
reader.readAsText(e.files[0]); reader.readAsText(e.files[0]);

View File

@ -37,7 +37,10 @@ describe('JSHandle', function () {
const { page } = getTestState(); const { page } = getTestState();
const navigatorHandle = await page.evaluateHandle(() => navigator); const navigatorHandle = await page.evaluateHandle(() => navigator);
const text = await page.evaluate((e) => e.userAgent, navigatorHandle); const text = await page.evaluate(
(e: Navigator) => e.userAgent,
navigatorHandle
);
expect(text).toContain('Mozilla'); expect(text).toContain('Mozilla');
}); });
it('should accept object handle to primitive types', async () => { it('should accept object handle to primitive types', async () => {
@ -75,7 +78,9 @@ describe('JSHandle', function () {
globalThis.FOO = 123; globalThis.FOO = 123;
return window; return window;
}); });
expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe(
123
);
}); });
it('should work with primitives', async () => { it('should work with primitives', async () => {
const { page } = getTestState(); const { page } = getTestState();
@ -84,7 +89,9 @@ describe('JSHandle', function () {
globalThis.FOO = 123; globalThis.FOO = 123;
return window; return window;
}); });
expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe(
123
);
}); });
}); });
@ -193,7 +200,10 @@ describe('JSHandle', function () {
const element = aHandle.asElement(); const element = aHandle.asElement();
expect(element).toBeTruthy(); expect(element).toBeTruthy();
expect( expect(
await page.evaluate((e) => e.nodeType === Node.TEXT_NODE, element) await page.evaluate(
(e: HTMLElement) => e.nodeType === Node.TEXT_NODE,
element
)
); );
}); });
}); });

View File

@ -387,7 +387,15 @@ describe('Keyboard', function () {
}); });
}); });
await page.keyboard.press('Meta'); await page.keyboard.press('Meta');
const [key, code, metaKey] = await page.evaluate('result'); // Have to do this because we lose a lot of type info when evaluating a
// string not a function. This is why functions are recommended rather than
// using strings (although we'll leave this test so we have coverage of both
// approaches.)
const [key, code, metaKey] = (await page.evaluate('result')) as [
string,
string,
boolean
];
if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS'); if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS');
else expect(key).toBe('Meta'); else expect(key).toBe('Meta');

View File

@ -61,7 +61,7 @@ describe('Mouse', function () {
}); });
}); });
await page.mouse.click(50, 60); await page.mouse.click(50, 60);
const event = await page.evaluate<MouseEvent>( const event = await page.evaluate<() => MouseEvent>(
() => globalThis.clickPromise () => globalThis.clickPromise
); );
expect(event.type).toBe('click'); expect(event.type).toBe('click');
@ -75,13 +75,15 @@ describe('Mouse', function () {
const { page, server } = getTestState(); const { page, server } = getTestState();
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
const { x, y, width, height } = await page.evaluate<Dimensions>(dimensions); const { x, y, width, height } = await page.evaluate<() => Dimensions>(
dimensions
);
const mouse = page.mouse; const mouse = page.mouse;
await mouse.move(x + width - 4, y + height - 4); await mouse.move(x + width - 4, y + height - 4);
await mouse.down(); await mouse.down();
await mouse.move(x + width + 100, y + height + 100); await mouse.move(x + width + 100, y + height + 100);
await mouse.up(); await mouse.up();
const newDimensions = await page.evaluate<Dimensions>(dimensions); const newDimensions = await page.evaluate<() => Dimensions>(dimensions);
expect(newDimensions.width).toBe(Math.round(width + 104)); expect(newDimensions.width).toBe(Math.round(width + 104));
expect(newDimensions.height).toBe(Math.round(height + 104)); expect(newDimensions.height).toBe(Math.round(height + 104));
}); });
@ -163,13 +165,15 @@ describe('Mouse', function () {
for (const [modifier, key] of modifiers) { for (const [modifier, key] of modifiers) {
await page.keyboard.down(modifier); await page.keyboard.down(modifier);
await page.click('#button-3'); await page.click('#button-3');
if (!(await page.evaluate((mod) => globalThis.lastEvent[mod], key))) if (
!(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
)
throw new Error(key + ' should be true'); throw new Error(key + ' should be true');
await page.keyboard.up(modifier); await page.keyboard.up(modifier);
} }
await page.click('#button-3'); await page.click('#button-3');
for (const [modifier, key] of modifiers) { for (const [modifier, key] of modifiers) {
if (await page.evaluate((mod) => globalThis.lastEvent[mod], key)) if (await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
throw new Error(modifiers[modifier] + ' should be false'); throw new Error(modifiers[modifier] + ' should be false');
} }
}); });

View File

@ -495,7 +495,7 @@ describe('navigation', function () {
const [response] = await Promise.all([ const [response] = await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.evaluate( page.evaluate(
(url) => (window.location.href = url), (url: string) => (window.location.href = url),
server.PREFIX + '/grid.html' server.PREFIX + '/grid.html'
), ),
]); ]);
@ -733,7 +733,7 @@ describe('navigation', function () {
const [response] = await Promise.all([ const [response] = await Promise.all([
frame.waitForNavigation(), frame.waitForNavigation(),
frame.evaluate( frame.evaluate(
(url) => (window.location.href = url), (url: string) => (window.location.href = url),
server.PREFIX + '/grid.html' server.PREFIX + '/grid.html'
), ),
]); ]);

View File

@ -27,6 +27,7 @@ import {
describeFailsFirefox, describeFailsFirefox,
} from './mocha-utils'; } from './mocha-utils';
import { Page, Metrics } from '../src/common/Page'; import { Page, Metrics } from '../src/common/Page';
import { JSHandle } from '../src/common/JSHandle';
describe('Page', function () { describe('Page', function () {
setupTestBrowserHooks(); setupTestBrowserHooks();
@ -397,7 +398,7 @@ describe('Page', function () {
const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const prototypeHandle = await page.evaluateHandle(() => Set.prototype);
const objectsHandle = await page.queryObjects(prototypeHandle); const objectsHandle = await page.queryObjects(prototypeHandle);
const count = await page.evaluate( const count = await page.evaluate(
(objects) => objects.length, (objects: JSHandle[]) => objects.length,
objectsHandle objectsHandle
); );
expect(count).toBe(1); expect(count).toBe(1);
@ -416,7 +417,7 @@ describe('Page', function () {
const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const prototypeHandle = await page.evaluateHandle(() => Set.prototype);
const objectsHandle = await page.queryObjects(prototypeHandle); const objectsHandle = await page.queryObjects(prototypeHandle);
const count = await page.evaluate( const count = await page.evaluate(
(objects) => objects.length, (objects: JSHandle[]) => objects.length,
objectsHandle objectsHandle
); );
expect(count).toBe(1); expect(count).toBe(1);
@ -515,7 +516,7 @@ describe('Page', function () {
const [message] = await Promise.all([ const [message] = await Promise.all([
waitEvent(page, 'console'), waitEvent(page, 'console'),
page.evaluate( page.evaluate(
async (url) => fetch(url).catch(() => {}), async (url: string) => fetch(url).catch(() => {}),
server.EMPTY_PAGE server.EMPTY_PAGE
), ),
]); ]);
@ -887,8 +888,8 @@ describe('Page', function () {
await page.exposeFunction('complexObject', function (a, b) { await page.exposeFunction('complexObject', function (a, b) {
return { x: a.x + b.x }; return { x: a.x + b.x };
}); });
const result = await page.evaluate<{ x: number }>(async () => const result = await page.evaluate<() => Promise<{ x: number }>>(
globalThis.complexObject({ x: 5 }, { x: 2 }) async () => globalThis.complexObject({ x: 5 }, { x: 2 })
); );
expect(result.x).toBe(7); expect(result.x).toBe(7);
}); });
@ -1334,7 +1335,7 @@ describe('Page', function () {
}); });
const styleHandle = await page.$('style'); const styleHandle = await page.$('style');
const styleContent = await page.evaluate( const styleContent = await page.evaluate(
(style) => style.innerHTML, (style: HTMLStyleElement) => style.innerHTML,
styleHandle styleHandle
); );
expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); expect(styleContent).toContain(path.join('assets', 'injectedstyle.css'));

View File

@ -103,7 +103,7 @@ describe('querySelector', function () {
const elements = await page.$$('div'); const elements = await page.$$('div');
expect(elements.length).toBe(2); expect(elements.length).toBe(2);
const promises = elements.map((element) => const promises = elements.map((element) =>
page.evaluate((e) => e.textContent, element) page.evaluate((e: HTMLElement) => e.textContent, element)
); );
expect(await Promise.all(promises)).toEqual(['A', 'B']); expect(await Promise.all(promises)).toEqual(['A', 'B']);
}); });
@ -151,7 +151,10 @@ describe('querySelector', function () {
const html = await page.$('html'); const html = await page.$('html');
const second = await html.$('.second'); const second = await html.$('.second');
const inner = await second.$('.inner'); const inner = await second.$('.inner');
const content = await page.evaluate((e) => e.textContent, inner); const content = await page.evaluate(
(e: HTMLElement) => e.textContent,
inner
);
expect(content).toBe('A'); expect(content).toBe('A');
}); });
@ -263,7 +266,7 @@ describe('querySelector', function () {
const elements = await html.$$('div'); const elements = await html.$$('div');
expect(elements.length).toBe(2); expect(elements.length).toBe(2);
const promises = elements.map((element) => const promises = elements.map((element) =>
page.evaluate((e) => e.textContent, element) page.evaluate((e: HTMLElement) => e.textContent, element)
); );
expect(await Promise.all(promises)).toEqual(['A', 'B']); expect(await Promise.all(promises)).toEqual(['A', 'B']);
}); });
@ -291,7 +294,10 @@ describe('querySelector', function () {
const html = await page.$('html'); const html = await page.$('html');
const second = await html.$x(`./body/div[contains(@class, 'second')]`); const second = await html.$x(`./body/div[contains(@class, 'second')]`);
const inner = await second[0].$x(`./div[contains(@class, 'inner')]`); const inner = await second[0].$x(`./div[contains(@class, 'inner')]`);
const content = await page.evaluate((e) => e.textContent, inner[0]); const content = await page.evaluate(
(e: HTMLElement) => e.textContent,
inner[0]
);
expect(content).toBe('A'); expect(content).toBe('A');
}); });

View File

@ -384,7 +384,7 @@ describe('request interception', function () {
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const text = await page.evaluate( const text = await page.evaluate(
(url) => fetch(url).then((r) => r.text()), (url: string) => fetch(url).then((r) => r.text()),
dataURL dataURL
); );
expect(text).toBe('<div>yo</div>'); expect(text).toBe('<div>yo</div>');

View File

@ -281,7 +281,10 @@ describe('Screenshots', function () {
await page.setContent('<h1>remove this</h1>'); await page.setContent('<h1>remove this</h1>');
const elementHandle = await page.$('h1'); const elementHandle = await page.$('h1');
await page.evaluate((element) => element.remove(), elementHandle); await page.evaluate(
(element: HTMLElement) => element.remove(),
elementHandle
);
const screenshotError = await elementHandle const screenshotError = await elementHandle
.screenshot() .screenshot()
.catch((error) => error); .catch((error) => error);

View File

@ -81,7 +81,7 @@ describe('Target', function () {
) )
.then((target) => target.page()), .then((target) => target.page()),
page.evaluate( page.evaluate(
(url) => window.open(url), (url: string) => window.open(url),
server.CROSS_PROCESS_PREFIX + '/empty.html' server.CROSS_PROCESS_PREFIX + '/empty.html'
), ),
]); ]);
@ -215,7 +215,7 @@ describe('Target', function () {
// Open a new page. Use window.open to connect to the page later. // Open a new page. Use window.open to connect to the page later.
await Promise.all([ await Promise.all([
page.evaluate( page.evaluate(
(url) => window.open(url), (url: string) => window.open(url),
server.PREFIX + '/one-style.html' server.PREFIX + '/one-style.html'
), ),
server.waitForRequest('/one-style.css'), server.waitForRequest('/one-style.css'),

View File

@ -264,7 +264,7 @@ describe('waittask specs', function () {
.waitForFunction((element) => !element.parentElement, {}, div) .waitForFunction((element) => !element.parentElement, {}, div)
.then(() => (resolved = true)); .then(() => (resolved = true));
expect(resolved).toBe(false); expect(resolved).toBe(false);
await page.evaluate((element) => element.remove(), div); await page.evaluate((element: HTMLElement) => element.remove(), div);
await waitForFunction; await waitForFunction;
}); });
it('should respect timeout', async () => { it('should respect timeout', async () => {
@ -351,9 +351,9 @@ describe('waittask specs', function () {
page.waitForSelector('.zombo'), page.waitForSelector('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`), page.setContent(`<div class='zombo'>anything</div>`),
]); ]);
expect(await page.evaluate((x) => x.textContent, handle)).toBe( expect(
'anything' await page.evaluate((x: HTMLElement) => x.textContent, handle)
); ).toBe('anything');
}); });
it('should resolve promise when node is added', async () => { it('should resolve promise when node is added', async () => {
@ -588,7 +588,10 @@ describe('waittask specs', function () {
const waitForSelector = page.waitForSelector('.zombo'); const waitForSelector = page.waitForSelector('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect( expect(
await page.evaluate((x) => x.textContent, await waitForSelector) await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForSelector
)
).toBe('anything'); ).toBe('anything');
}); });
it('should have correct stack trace for timeout', async () => { it('should have correct stack trace for timeout', async () => {
@ -616,7 +619,10 @@ describe('waittask specs', function () {
'//p[normalize-space(.)="hello world"]' '//p[normalize-space(.)="hello world"]'
); );
expect( expect(
await page.evaluate((x) => x.textContent, await waitForXPath) await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
).toBe('hello world '); ).toBe('hello world ');
}); });
it('should respect timeout', async () => { it('should respect timeout', async () => {
@ -683,7 +689,10 @@ describe('waittask specs', function () {
const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect( expect(
await page.evaluate((x) => x.textContent, await waitForXPath) await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
).toBe('anything'); ).toBe('anything');
}); });
it('should allow you to select a text node', async () => { it('should allow you to select a text node', async () => {
@ -701,7 +710,10 @@ describe('waittask specs', function () {
await page.setContent(`<div>some text</div>`); await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForXPath('/html/body/div'); const waitForXPath = page.waitForXPath('/html/body/div');
expect( expect(
await page.evaluate((x) => x.textContent, await waitForXPath) await page.evaluate(
(x: HTMLElement) => x.textContent,
await waitForXPath
)
).toBe('some text'); ).toBe('some text');
}); });
}); });

View File

@ -60,7 +60,10 @@ describeFailsFirefox('Workers', function () {
const workerDestroyedPromise = new Promise((x) => const workerDestroyedPromise = new Promise((x) =>
page.once('workerdestroyed', x) page.once('workerdestroyed', x)
); );
await page.evaluate((workerObj) => workerObj.terminate(), workerObj); await page.evaluate(
(workerObj: Worker) => workerObj.terminate(),
workerObj
);
expect(await workerDestroyedPromise).toBe(worker); expect(await workerDestroyedPromise).toBe(worker);
const error = await workerThisObj const error = await workerThisObj
.getProperty('self') .getProperty('self')