mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: add page.createCDPSession method (#10515)
This commit is contained in:
parent
c5016cc670
commit
d0c5b8e089
19
docs/api/puppeteer.page.createcdpsession.md
Normal file
19
docs/api/puppeteer.page.createcdpsession.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_label: Page.createCDPSession
|
||||
---
|
||||
|
||||
# Page.createCDPSession() method
|
||||
|
||||
Creates a Chrome Devtools Protocol session attached to the page.
|
||||
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
class Page {
|
||||
createCDPSession(): Promise<CDPSession>;
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
|
||||
Promise<[CDPSession](./puppeteer.cdpsession.md)>
|
@ -92,6 +92,7 @@ page.off('request', logRequest);
|
||||
| [close(options)](./puppeteer.page.close.md) | | |
|
||||
| [content()](./puppeteer.page.content.md) | | The full HTML contents of the page, including the DOCTYPE. |
|
||||
| [cookies(urls)](./puppeteer.page.cookies.md) | | If no URLs are specified, this method returns cookies for the current page URL. If URLs are specified, only cookies for those URLs are returned. |
|
||||
| [createCDPSession()](./puppeteer.page.createcdpsession.md) | | Creates a Chrome Devtools Protocol session attached to the page. |
|
||||
| [createPDFStream(options)](./puppeteer.page.createpdfstream.md) | | Generates a PDF of the page with the <code>print</code> CSS media type. |
|
||||
| [deleteCookie(cookies)](./puppeteer.page.deletecookie.md) | | |
|
||||
| [emulate(device)](./puppeteer.page.emulate.md) | | <p>Emulates a given device's metrics and user agent.</p><p>To aid emulation, Puppeteer provides a list of known devices that can be via [KnownDevices](./puppeteer.knowndevices.md).</p> |
|
||||
|
@ -21,6 +21,7 @@ import {Protocol} from 'devtools-protocol';
|
||||
import type {HTTPRequest} from '../api/HTTPRequest.js';
|
||||
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import type {Accessibility} from '../common/Accessibility.js';
|
||||
import type {CDPSession} from '../common/Connection.js';
|
||||
import type {ConsoleMessage} from '../common/ConsoleMessage.js';
|
||||
import type {Coverage} from '../common/Coverage.js';
|
||||
import {Device} from '../common/Device.js';
|
||||
@ -622,6 +623,13 @@ export class Page extends EventEmitter {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Chrome Devtools Protocol session attached to the page.
|
||||
*/
|
||||
createCDPSession(): Promise<CDPSession> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc Keyboard}
|
||||
*/
|
||||
|
@ -862,6 +862,10 @@ export class CDPPage extends Page {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
override async createCDPSession(): Promise<CDPSession> {
|
||||
return await this.target().createCDPSession();
|
||||
}
|
||||
|
||||
override async waitForRequest(
|
||||
urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise<boolean>),
|
||||
options: {timeout?: number} = {}
|
||||
|
@ -48,6 +48,9 @@ export class Browser extends BrowserBase {
|
||||
'cdp.Runtime.executionContextsCleared',
|
||||
// Tracing
|
||||
'cdp.Tracing.tracingComplete',
|
||||
// TODO: subscribe to all CDP events in the future.
|
||||
'cdp.Network.requestWillBeSent',
|
||||
'cdp.Debugger.scriptParsed',
|
||||
];
|
||||
|
||||
#browserName = '';
|
||||
|
@ -5,7 +5,7 @@ import {WaitForOptions} from '../../api/Page.js';
|
||||
import {assert} from '../../util/assert.js';
|
||||
import {Deferred} from '../../util/Deferred.js';
|
||||
import type {CDPSession, Connection as CDPConnection} from '../Connection.js';
|
||||
import {ProtocolError, TimeoutError} from '../Errors.js';
|
||||
import {ProtocolError, TargetCloseError, TimeoutError} from '../Errors.js';
|
||||
import {EventEmitter} from '../EventEmitter.js';
|
||||
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
@ -13,6 +13,7 @@ import {getPageContent, setPageContent, waitWithTimeout} from '../util.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {Realm} from './Realm.js';
|
||||
import {debugError} from './utils.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -36,35 +37,53 @@ const lifeCycleToReadinessState = new Map<
|
||||
['domcontentloaded', 'interactive'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const cdpSessions = new Map<string, CDPSessionWrapper>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class CDPSessionWrapper extends EventEmitter implements CDPSession {
|
||||
#context: BrowsingContext;
|
||||
#sessionId = Deferred.create<string>();
|
||||
#detached = false;
|
||||
|
||||
constructor(context: BrowsingContext) {
|
||||
constructor(context: BrowsingContext, sessionId?: string) {
|
||||
super();
|
||||
this.#context = context;
|
||||
if (sessionId) {
|
||||
this.#sessionId.resolve(sessionId);
|
||||
cdpSessions.set(sessionId, this);
|
||||
} else {
|
||||
context.connection
|
||||
.send('cdp.getSession', {
|
||||
context: context.id,
|
||||
})
|
||||
.then(session => {
|
||||
this.#sessionId.resolve(session.result.session!);
|
||||
cdpSessions.set(session.result.session!, this);
|
||||
})
|
||||
.catch(err => {
|
||||
this.#sessionId.reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
connection(): CDPConnection | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async send<T extends keyof ProtocolMapping.Commands>(
|
||||
method: T,
|
||||
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
|
||||
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
||||
if (this.#detached) {
|
||||
throw new TargetCloseError(
|
||||
`Protocol error (${method}): Session closed. Most likely the page has been closed.`
|
||||
);
|
||||
}
|
||||
const session = await this.#sessionId.valueOrThrow();
|
||||
const result = await this.#context.connection.send('cdp.sendCommand', {
|
||||
method: method,
|
||||
@ -74,8 +93,12 @@ export class CDPSessionWrapper extends EventEmitter implements CDPSession {
|
||||
return result.result;
|
||||
}
|
||||
|
||||
detach(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
async detach(): Promise<void> {
|
||||
cdpSessions.delete(this.id());
|
||||
await this.#context.cdpSession.send('Target.detachFromTarget', {
|
||||
sessionId: this.id(),
|
||||
});
|
||||
this.#detached = true;
|
||||
}
|
||||
|
||||
id(): string {
|
||||
@ -244,6 +267,7 @@ export class BrowsingContext extends Realm {
|
||||
dispose(): void {
|
||||
this.removeAllListeners();
|
||||
this.connection.unregisterBrowsingContexts(this.#id);
|
||||
void this.#cdpSession.detach().catch(debugError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import {ConnectionTransport} from '../ConnectionTransport.js';
|
||||
import {debug} from '../Debug.js';
|
||||
import {EventEmitter} from '../EventEmitter.js';
|
||||
|
||||
import {BrowsingContext} from './BrowsingContext.js';
|
||||
import {BrowsingContext, cdpSessions} from './BrowsingContext.js';
|
||||
|
||||
const debugProtocolSend = debug('puppeteer:webDriverBiDi:SEND ►');
|
||||
const debugProtocolReceive = debug('puppeteer:webDriverBiDi:RECV ◀');
|
||||
@ -235,15 +235,9 @@ export class Connection extends EventEmitter {
|
||||
} else if ('source' in event.params && event.params.source.context) {
|
||||
context = this.#browsingContexts.get(event.params.source.context);
|
||||
} else if (isCDPEvent(event)) {
|
||||
// TODO: this is not a good solution and we need to find a better one.
|
||||
// Perhaps we need to have a dedicated CDP event emitter or emulate
|
||||
// the CDPSession interface with BiDi?.
|
||||
const cdpSessionId = event.params.session;
|
||||
for (const context of this.#browsingContexts.values()) {
|
||||
if (context.cdpSession?.id() === cdpSessionId) {
|
||||
context.cdpSession!.emit(event.params.event, event.params.params);
|
||||
}
|
||||
}
|
||||
cdpSessions
|
||||
.get(event.params.session)
|
||||
?.emit(event.params.event, event.params.params);
|
||||
}
|
||||
context?.emit(event.method, event.params);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
import {assert} from '../../util/assert.js';
|
||||
import {Deferred} from '../../util/Deferred.js';
|
||||
import {Accessibility} from '../Accessibility.js';
|
||||
import {CDPSession} from '../Connection.js';
|
||||
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js';
|
||||
import {Coverage} from '../Coverage.js';
|
||||
import {EmulationManager} from '../EmulationManager.js';
|
||||
@ -52,7 +53,7 @@ import {
|
||||
|
||||
import {Browser} from './Browser.js';
|
||||
import {BrowserContext} from './BrowserContext.js';
|
||||
import {BrowsingContext} from './BrowsingContext.js';
|
||||
import {BrowsingContext, CDPSessionWrapper} from './BrowsingContext.js';
|
||||
import {Connection} from './Connection.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
@ -632,6 +633,16 @@ export class Page extends PageBase {
|
||||
override title(): Promise<string> {
|
||||
return this.mainFrame().title();
|
||||
}
|
||||
|
||||
override async createCDPSession(): Promise<CDPSession> {
|
||||
const {sessionId} = await this.mainFrame()
|
||||
.context()
|
||||
.cdpSession.send('Target.attachToTarget', {
|
||||
targetId: this.mainFrame()._id,
|
||||
flatten: true,
|
||||
});
|
||||
return new CDPSessionWrapper(this.mainFrame().context(), sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
function isConsoleLogEntry(
|
||||
|
@ -1667,18 +1667,48 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should be able to detach session",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should enable and disable domains independently",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should enable and disable domains independently",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL", "PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should throw nice errors",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should be able to connect using browserUrl, with and without trailing slash",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
@ -26,7 +26,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should work', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
|
||||
await Promise.all([
|
||||
client.send('Runtime.enable'),
|
||||
@ -56,7 +56,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should send events', async () => {
|
||||
const {page, server} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
await client.send('Network.enable');
|
||||
const events: unknown[] = [];
|
||||
client.on('Network.requestWillBeSent', event => {
|
||||
@ -71,7 +71,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should enable and disable domains independently', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
await client.send('Runtime.enable');
|
||||
await client.send('Debugger.enable');
|
||||
// JS coverage enables and then disables Debugger domain.
|
||||
@ -88,7 +88,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should be able to detach session', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
await client.send('Runtime.enable');
|
||||
const evalResponse = await client.send('Runtime.evaluate', {
|
||||
expression: '1 + 2',
|
||||
@ -112,7 +112,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should throw nice errors', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
const error = await theSourceOfTheProblems().catch(error => {
|
||||
return error;
|
||||
});
|
||||
@ -130,7 +130,7 @@ describe('Target.createCDPSession', function () {
|
||||
it('should expose the underlying connection', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
const client = await page.createCDPSession();
|
||||
expect(client.connection()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user