fix: type page event listeners correctly (#6891)

This PR fixes the fact that currently if you have:

```ts
page.on('request', request => {

})
```

Then `request` will be typed as `any`. We can fix this by defining an
interface of event name => callback argument type, and looking that up
when you call `page.on`.

Also includes a drive-by fix to ensure we convert response headers to
strings, and updates the types accordingly.
This commit is contained in:
Jack Franklin 2021-03-25 11:26:35 +00:00 committed by GitHub
parent e31e68dfa1
commit 866d34ee11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 3 deletions

View File

@ -40,7 +40,10 @@ export interface ContinueRequestOverrides {
*/ */
export interface ResponseForRequest { export interface ResponseForRequest {
status: number; status: number;
headers: Record<string, string>; /**
* Optional response headers. All values are converted to strings.
*/
headers: Record<string, unknown>;
contentType: string; contentType: string;
body: string | Buffer; body: string | Buffer;
} }
@ -346,7 +349,7 @@ export class HTTPRequest {
* *
* @param response - the response to fulfill the request with. * @param response - the response to fulfill the request with.
*/ */
async respond(response: ResponseForRequest): Promise<void> { async respond(response: Partial<ResponseForRequest>): Promise<void> {
// Mocking responses for dataURL requests is not currently supported. // Mocking responses for dataURL requests is not currently supported.
if (this._url.startsWith('data:')) return; if (this._url.startsWith('data:')) return;
assert(this._allowInterception, 'Request Interception is not enabled!'); assert(this._allowInterception, 'Request Interception is not enabled!');
@ -361,7 +364,9 @@ export class HTTPRequest {
const responseHeaders: Record<string, string> = {}; const responseHeaders: Record<string, string> = {};
if (response.headers) { if (response.headers) {
for (const header of Object.keys(response.headers)) for (const header of Object.keys(response.headers))
responseHeaders[header.toLowerCase()] = response.headers[header]; responseHeaders[header.toLowerCase()] = String(
response.headers[header]
);
} }
if (response.contentType) if (response.contentType)
responseHeaders['content-type'] = response.contentType; responseHeaders['content-type'] = response.contentType;

View File

@ -329,6 +329,35 @@ export const enum PageEmittedEvents {
WorkerDestroyed = 'workerdestroyed', WorkerDestroyed = 'workerdestroyed',
} }
/**
* Denotes the objects received by callback functions for page events.
*
* See {@link PageEmittedEvents} for more detail on the events and when they are
* emitted.
* @public
*/
export interface PageEventObject {
close: never;
console: ConsoleMessage;
dialog: Dialog;
domcontentloaded: never;
error: Error;
frameattached: Frame;
framedetached: Frame;
framenavigated: Frame;
load: never;
metrics: { title: string; metrics: Metrics };
pageerror: Error;
popup: Page;
request: HTTPRequest;
response: HTTPResponse;
requestfailed: HTTPRequest;
requestfinished: HTTPRequest;
requestservedfromcache: HTTPRequest;
workercreated: WebWorker;
workerdestroyed: WebWorker;
}
class ScreenshotTaskQueue { class ScreenshotTaskQueue {
_chain: Promise<Buffer | string | void>; _chain: Promise<Buffer | string | void>;
@ -559,6 +588,27 @@ export class Page extends EventEmitter {
return this._javascriptEnabled; return this._javascriptEnabled;
} }
/**
* Listen to page events.
*/
public on<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): EventEmitter {
// Note: this method only exists to define the types; we delegate the impl
// to EventEmitter.
return super.on(eventName, handler);
}
public once<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): EventEmitter {
// Note: this method only exists to define the types; we delegate the impl
// to EventEmitter.
return super.once(eventName, handler);
}
/** /**
* @param options - Optional waiting parameters * @param options - Optional waiting parameters
* @returns Resolves after a page requests a file picker. * @returns Resolves after a page requests a file picker.

View File

@ -5,6 +5,9 @@ async function run() {
const devices = puppeteer.devices; const devices = puppeteer.devices;
console.log(devices); console.log(devices);
const page = await browser.newPage(); const page = await browser.newPage();
page.on('request', (request) => {
const resourceType = request.resourceType();
});
const div = (await page.$('div')) as puppeteer.ElementHandle< const div = (await page.$('div')) as puppeteer.ElementHandle<
HTMLAnchorElement HTMLAnchorElement
>; >;

View File

@ -864,6 +864,27 @@ function compareDocumentations(actual, expected) {
expectedName: 'Array<Permission>', expectedName: 'Array<Permission>',
}, },
], ],
[
'Method HTTPRequest.respond() response.body',
{
actualName: 'string|Buffer',
expectedName: 'Object',
},
],
[
'Method HTTPRequest.respond() response.contentType',
{
actualName: 'string',
expectedName: 'Object',
},
],
[
'Method HTTPRequest.respond() response.status',
{
actualName: 'number',
expectedName: 'Object',
},
],
]); ]);
const expectedForSource = expectedNamingMismatches.get(source); const expectedForSource = expectedNamingMismatches.get(source);