feat: support timeouts per CDP command (#11595)

This commit is contained in:
Alex Rudenko 2024-01-02 11:00:07 +01:00 committed by GitHub
parent acf1518deb
commit c660d4001d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 86 additions and 27 deletions

View File

@ -80,6 +80,7 @@ sidebar_label: API
| [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) | Launcher options that only apply to Chrome. | | [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) | Launcher options that only apply to Chrome. |
| [CDPSessionEvents](./puppeteer.cdpsessionevents.md) | | | [CDPSessionEvents](./puppeteer.cdpsessionevents.md) | |
| [ClickOptions](./puppeteer.clickoptions.md) | | | [ClickOptions](./puppeteer.clickoptions.md) | |
| [CommandOptions](./puppeteer.commandoptions.md) | |
| [CommonEventEmitter](./puppeteer.commoneventemitter.md) | | | [CommonEventEmitter](./puppeteer.commoneventemitter.md) | |
| [Configuration](./puppeteer.configuration.md) | <p>Defines options to configure Puppeteer's behavior during installation and runtime.</p><p>See individual properties for more information.</p> | | [Configuration](./puppeteer.configuration.md) | <p>Defines options to configure Puppeteer's behavior during installation and runtime.</p><p>See individual properties for more information.</p> |
| [ConnectionTransport](./puppeteer.connectiontransport.md) | | | [ConnectionTransport](./puppeteer.connectiontransport.md) | |

View File

@ -40,8 +40,8 @@ await client.send('Animation.setPlaybackRate', {
## Methods ## Methods
| Method | Modifiers | Description | | Method | Modifiers | Description |
| --------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| [connection()](./puppeteer.cdpsession.connection.md) | | | | [connection()](./puppeteer.cdpsession.connection.md) | | |
| [detach()](./puppeteer.cdpsession.detach.md) | | Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages. | | [detach()](./puppeteer.cdpsession.detach.md) | | Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages. |
| [id()](./puppeteer.cdpsession.id.md) | | Returns the session's id. | | [id()](./puppeteer.cdpsession.id.md) | | Returns the session's id. |
| [send(method, paramArgs)](./puppeteer.cdpsession.send.md) | | | | [send(method, params, options)](./puppeteer.cdpsession.send.md) | | |

View File

@ -10,7 +10,8 @@ sidebar_label: CDPSession.send
class CDPSession { class CDPSession {
abstract send<T extends keyof ProtocolMapping.Commands>( abstract send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType'] params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']>; ): Promise<ProtocolMapping.Commands[T]['returnType']>;
} }
``` ```
@ -18,9 +19,10 @@ class CDPSession {
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | --------------------------------------------- | ----------- | | --------- | -------------------------------------------------- | ------------ |
| method | T | | | method | T | |
| paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | | | params | ProtocolMapping.Commands\[T\]\['paramsType'\]\[0\] | _(Optional)_ |
| options | [CommandOptions](./puppeteer.commandoptions.md) | _(Optional)_ |
**Returns:** **Returns:**

View File

@ -0,0 +1,17 @@
---
sidebar_label: CommandOptions
---
# CommandOptions interface
#### Signature:
```typescript
export interface CommandOptions
```
## Properties
| Property | Modifiers | Type | Description | Default |
| -------- | --------- | ------ | ----------- | ------- |
| timeout | | number | | |

View File

@ -31,6 +31,6 @@ export declare class Connection extends EventEmitter<CDPSessionEvents>
| [createSession(targetInfo)](./puppeteer.connection.createsession.md) | | | | [createSession(targetInfo)](./puppeteer.connection.createsession.md) | | |
| [dispose()](./puppeteer.connection.dispose.md) | | | | [dispose()](./puppeteer.connection.dispose.md) | | |
| [fromSession(session)](./puppeteer.connection.fromsession.md) | <code>static</code> | | | [fromSession(session)](./puppeteer.connection.fromsession.md) | <code>static</code> | |
| [send(method, paramArgs)](./puppeteer.connection.send.md) | | | | [send(method, params, options)](./puppeteer.connection.send.md) | | |
| [session(sessionId)](./puppeteer.connection.session.md) | | | | [session(sessionId)](./puppeteer.connection.session.md) | | |
| [url()](./puppeteer.connection.url.md) | | | | [url()](./puppeteer.connection.url.md) | | |

View File

@ -10,7 +10,8 @@ sidebar_label: Connection.send
class Connection { class Connection {
send<T extends keyof ProtocolMapping.Commands>( send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType'] params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']>; ): Promise<ProtocolMapping.Commands[T]['returnType']>;
} }
``` ```
@ -18,9 +19,10 @@ class Connection {
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | --------------------------------------------- | ----------- | | --------- | -------------------------------------------------- | ------------ |
| method | T | | | method | T | |
| paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | | | params | ProtocolMapping.Commands\[T\]\['paramsType'\]\[0\] | _(Optional)_ |
| options | [CommandOptions](./puppeteer.commandoptions.md) | _(Optional)_ |
**Returns:** **Returns:**

View File

@ -48,6 +48,13 @@ export interface CDPSessionEvents
[CDPSessionEvent.SessionDetached]: CDPSession; [CDPSessionEvent.SessionDetached]: CDPSession;
} }
/**
* @public
*/
export interface CommandOptions {
timeout: number;
}
/** /**
* The `CDPSession` instances are used to talk raw Chrome Devtools Protocol. * The `CDPSession` instances are used to talk raw Chrome Devtools Protocol.
* *
@ -97,7 +104,8 @@ export abstract class CDPSession extends EventEmitter<CDPSessionEvents> {
abstract send<T extends keyof ProtocolMapping.Commands>( abstract send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType'] params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']>; ): Promise<ProtocolMapping.Commands[T]['returnType']>;
/** /**

View File

@ -20,6 +20,7 @@ import {
type CDPEvents, type CDPEvents,
CDPSession, CDPSession,
CDPSessionEvent, CDPSessionEvent,
type CommandOptions,
} from '../api/CDPSession.js'; } from '../api/CDPSession.js';
import {CallbackRegistry} from '../common/CallbackRegistry.js'; import {CallbackRegistry} from '../common/CallbackRegistry.js';
import {TargetCloseError} from '../common/Errors.js'; import {TargetCloseError} from '../common/Errors.js';
@ -91,7 +92,8 @@ export class CdpCDPSession extends CDPSession {
override send<T extends keyof ProtocolMapping.Commands>( override send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType'] params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']> { ): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this.#connection) { if (!this.#connection) {
return Promise.reject( return Promise.reject(
@ -100,13 +102,12 @@ export class CdpCDPSession extends CDPSession {
) )
); );
} }
// See the comment in Connection#send explaining why we do this.
const params = paramArgs.length ? paramArgs[0] : undefined;
return this.#connection._rawSend( return this.#connection._rawSend(
this.#callbacks, this.#callbacks,
method, method,
params, params,
this.#sessionId this.#sessionId,
options
); );
} }

View File

@ -17,6 +17,7 @@
import type {Protocol} from 'devtools-protocol'; import type {Protocol} from 'devtools-protocol';
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import type {CommandOptions} from '../api/CDPSession.js';
import { import {
CDPSessionEvent, CDPSessionEvent,
type CDPSession, type CDPSession,
@ -104,7 +105,8 @@ export class Connection extends EventEmitter<CDPSessionEvents> {
send<T extends keyof ProtocolMapping.Commands>( send<T extends keyof ProtocolMapping.Commands>(
method: T, method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType'] params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']> { ): Promise<ProtocolMapping.Commands[T]['returnType']> {
// There is only ever 1 param arg passed, but the Protocol defines it as an // There is only ever 1 param arg passed, but the Protocol defines it as an
// array of 0 or 1 items See this comment: // array of 0 or 1 items See this comment:
@ -112,8 +114,7 @@ export class Connection extends EventEmitter<CDPSessionEvents> {
// which explains why the protocol defines the params this way for better // which explains why the protocol defines the params this way for better
// type-inference. // type-inference.
// So now we check if there are any params or not and deal with them accordingly. // So now we check if there are any params or not and deal with them accordingly.
const params = paramArgs.length ? paramArgs[0] : undefined; return this._rawSend(this.#callbacks, method, params, undefined, options);
return this._rawSend(this.#callbacks, method, params);
} }
/** /**
@ -123,9 +124,10 @@ export class Connection extends EventEmitter<CDPSessionEvents> {
callbacks: CallbackRegistry, callbacks: CallbackRegistry,
method: T, method: T,
params: ProtocolMapping.Commands[T]['paramsType'][0], params: ProtocolMapping.Commands[T]['paramsType'][0],
sessionId?: string sessionId?: string,
options?: CommandOptions
): Promise<ProtocolMapping.Commands[T]['returnType']> { ): Promise<ProtocolMapping.Commands[T]['returnType']> {
return callbacks.create(method, this.#timeout, id => { return callbacks.create(method, options?.timeout ?? this.#timeout, id => {
const stringifiedMessage = JSON.stringify({ const stringifiedMessage = JSON.stringify({
method, method,
params, params,

View File

@ -1445,6 +1445,12 @@
"parameters": ["chrome", "webDriverBiDi"], "parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"] "expectations": ["PASS"]
}, },
{
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should respect custom timeout",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{ {
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events", "testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],

View File

@ -127,6 +127,26 @@ describe('Target.createCDPSession', function () {
} }
}); });
it('should respect custom timeout', async () => {
const {page} = await getTestState();
const client = await page.createCDPSession();
await expect(
client.send(
'Runtime.evaluate',
{
expression: 'new Promise(resolve => {})',
awaitPromise: true,
},
{
timeout: 50,
}
)
).rejects.toThrowError(
`Runtime.evaluate timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.`
);
});
it('should expose the underlying connection', async () => { it('should expose the underlying connection', async () => {
const {page} = await getTestState(); const {page} = await getTestState();