Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion new-docs/puppeteer.evaluatefn.md
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```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);
```
2 changes: 1 addition & 1 deletion new-docs/puppeteer.evaluatefnreturntype.md
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```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;
```
8 changes: 4 additions & 4 deletions new-docs/puppeteer.frame.evaluate.md
Expand Up @@ -7,19 +7,19 @@
<b>Signature:</b>

```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

| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | a function that is run within the frame |
| args | unknown\[\] | arguments to be passed to the pageFunction |
| pageFunction | T | a function that is run within the frame |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | arguments to be passed to the pageFunction |

<b>Returns:</b>

Promise&lt;ReturnType&gt;
Promise&lt;[UnwrapPromiseLike](./puppeteer.unwrappromiselike.md)<!-- -->&lt;[EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)<!-- -->&lt;T&gt;&gt;&gt;

## Remarks

Expand Down
4 changes: 2 additions & 2 deletions new-docs/puppeteer.jshandle.evaluate.md
Expand Up @@ -9,7 +9,7 @@ This method passes this handle as the first argument to `pageFunction`<!-- -->.
<b>Signature:</b>

```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
Expand All @@ -21,7 +21,7 @@ evaluate<T extends EvaluateFn>(pageFunction: T | string, ...args: SerializableOr

<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

Expand Down
1 change: 1 addition & 0 deletions new-docs/puppeteer.md
Expand Up @@ -109,5 +109,6 @@
| [Serializable](./puppeteer.serializable.md) | |
| [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md) | |
| [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 |

14 changes: 10 additions & 4 deletions new-docs/puppeteer.page.evaluate.md
Expand Up @@ -7,19 +7,19 @@
<b>Signature:</b>

```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

| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | a function that is run within the page |
| args | unknown\[\] | arguments to be passed to the pageFunction |
| pageFunction | T | a function that is run within the page |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | arguments to be passed to the pageFunction |

<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`<!-- -->.

Expand Down Expand Up @@ -47,6 +47,12 @@ You can pass a string instead of a function (although functions are recommended
```
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
Expand Down
2 changes: 1 addition & 1 deletion new-docs/puppeteer.serializable.md
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```typescript
export declare type Serializable = number | string | boolean | null | JSONArray | JSONObject;
export declare type Serializable = number | string | boolean | null | BigInt | JSONArray | JSONObject;
```
11 changes: 11 additions & 0 deletions new-docs/puppeteer.unwrappromiselike.md
@@ -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;
```
18 changes: 12 additions & 6 deletions src/common/DOMWorld.ts
Expand Up @@ -28,6 +28,9 @@ import {
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes';
import { isNode } from '../environment';

Expand Down Expand Up @@ -118,12 +121,15 @@ export class DOMWorld {
return context.evaluateHandle(pageFunction, ...args);
}

async evaluate<ReturnType extends any>(
pageFunction: Function | string,
...args: unknown[]
): Promise<ReturnType> {
async evaluate<T extends EvaluateFn>(
pageFunction: T,
...args: SerializableOrJSHandle[]
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
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> {
Expand Down Expand Up @@ -206,7 +212,7 @@ export class DOMWorld {
} = options;
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
// lifecycle event. @see https://crrev.com/608658
await this.evaluate((html) => {
await this.evaluate<(x: string) => void>((html) => {
document.open();
document.write(html);
document.close();
Expand Down
10 changes: 8 additions & 2 deletions src/common/EvalTypes.ts
Expand Up @@ -19,12 +19,17 @@ import { JSHandle, ElementHandle } from './JSHandle';
/**
* @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
*/
export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (
...args: any[]
...args: unknown[]
) => infer R
? R
: unknown;
Expand All @@ -42,6 +47,7 @@ export type Serializable =
| string
| boolean
| null
| BigInt
| JSONArray
| JSONObject;

Expand Down
13 changes: 8 additions & 5 deletions src/common/FrameManager.ts
Expand Up @@ -32,6 +32,9 @@ import {
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes';

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

/**
Expand Down
15 changes: 8 additions & 7 deletions src/common/JSHandle.ts
Expand Up @@ -29,6 +29,7 @@ import {
EvaluateFnReturnType,
EvaluateHandleFn,
WrapElementHandle,
UnwrapPromiseLike,
} from './EvalTypes';

export interface BoxModel {
Expand Down Expand Up @@ -153,12 +154,10 @@ export class JSHandle {
async evaluate<T extends EvaluateFn>(
pageFunction: T | string,
...args: SerializableOrJSHandle[]
): Promise<EvaluateFnReturnType<T>> {
return await this.executionContext().evaluate<EvaluateFnReturnType<T>>(
pageFunction,
this,
...args
);
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
return await this.executionContext().evaluate<
UnwrapPromiseLike<EvaluateFnReturnType<T>>
>(pageFunction, this, ...args);
}

/**
Expand Down Expand Up @@ -619,7 +618,9 @@ export class ElementHandle<
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
*/
async focus(): Promise<void> {
await this.evaluate((element) => element.focus());
await this.evaluate<(element: HTMLElement) => void>((element) =>
element.focus()
);
}

/**
Expand Down
22 changes: 15 additions & 7 deletions src/common/Page.ts
Expand Up @@ -45,6 +45,9 @@ import {
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes';

const writeFileAsync = promisify(fs.writeFile);
Expand Down Expand Up @@ -1519,6 +1522,13 @@ export class Page extends EventEmitter {
* 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
*
* {@link ElementHandle} instances (including {@link JSHandle}s) can be passed
Expand All @@ -1535,13 +1545,11 @@ export class Page extends EventEmitter {
*
* @returns the return value of `pageFunction`.
*/
async evaluate<ReturnType extends any>(
pageFunction: Function | string,
...args: unknown[]
): Promise<ReturnType> {
return this._frameManager
.mainFrame()
.evaluate<ReturnType>(pageFunction, ...args);
async evaluate<T extends EvaluateFn>(
pageFunction: T,
...args: SerializableOrJSHandle[]
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
return this._frameManager.mainFrame().evaluate<T>(pageFunction, ...args);
}

async evaluateOnNewDocument(
Expand Down
5 changes: 4 additions & 1 deletion test/browsercontext.spec.ts
Expand Up @@ -66,7 +66,10 @@ describe('BrowserContext', function () {
await page.goto(server.EMPTY_PAGE);
const [popupTarget] = await Promise.all([
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);
await context.close();
Expand Down
8 changes: 4 additions & 4 deletions test/cookies.spec.ts
Expand Up @@ -389,9 +389,9 @@ describe('Cookie specs', () => {

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

try {
await page.goto(httpsServer.PREFIX + '/grid.html');
await page.evaluate((src) => {
await page.evaluate<(src: string) => Promise<void>>((src) => {
let fulfill;
const promise = new Promise((x) => (fulfill = x));
const promise = new Promise<void>((x) => (fulfill = x));
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = fulfill;
Expand Down
2 changes: 1 addition & 1 deletion test/coverage.spec.ts
Expand Up @@ -264,7 +264,7 @@ describe('Coverage specs', function () {
const { page, server } = getTestState();

await page.coverage.startCSSCoverage();
await page.evaluate(async (url) => {
await page.evaluate<(url: string) => Promise<void>>(async (url) => {
document.body.textContent = 'hello, world';

const link = document.createElement('link');
Expand Down