From 66aa77003880a1458e14b47a3ed87856fd3a1933 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Tue, 2 Jan 2024 11:32:03 +0100 Subject: [PATCH] feat: allow converting other targets to pages (#11604) --- docs/api/puppeteer.target.aspage.md | 19 +++++++++++++++++++ docs/api/puppeteer.target.md | 21 +++++++++++---------- packages/puppeteer-core/src/api/Target.ts | 7 +++++++ packages/puppeteer-core/src/bidi/Target.ts | 5 +++-- packages/puppeteer-core/src/cdp/Target.ts | 10 ++++++++++ test/src/headful.spec.ts | 20 ++++++++++++++++++++ 6 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 docs/api/puppeteer.target.aspage.md diff --git a/docs/api/puppeteer.target.aspage.md b/docs/api/puppeteer.target.aspage.md new file mode 100644 index 00000000000..28f13efb626 --- /dev/null +++ b/docs/api/puppeteer.target.aspage.md @@ -0,0 +1,19 @@ +--- +sidebar_label: Target.asPage +--- + +# Target.asPage() method + +Forcefully creates a page for a target of any type. It is useful if you want to handle a CDP target of type `other` as a page. If you deal with a regular page target, use [Target.page()](./puppeteer.target.page.md). + +#### Signature: + +```typescript +class Target { + abstract asPage(): Promise; +} +``` + +**Returns:** + +Promise<[Page](./puppeteer.page.md)> diff --git a/docs/api/puppeteer.target.md b/docs/api/puppeteer.target.md index 547a6757a9c..d0834338275 100644 --- a/docs/api/puppeteer.target.md +++ b/docs/api/puppeteer.target.md @@ -18,13 +18,14 @@ The constructor for this class is marked as internal. Third-party code should no ## Methods -| Method | Modifiers | Description | -| ------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [browser()](./puppeteer.target.browser.md) | | Get the browser the target belongs to. | -| [browserContext()](./puppeteer.target.browsercontext.md) | | Get the browser context the target belongs to. | -| [createCDPSession()](./puppeteer.target.createcdpsession.md) | | Creates a Chrome Devtools Protocol session attached to the target. | -| [opener()](./puppeteer.target.opener.md) | | Get the target that opened this target. Top-level targets return null. | -| [page()](./puppeteer.target.page.md) | | If the target is not of type "page", "webview" or "background_page", returns null. | -| [type()](./puppeteer.target.type.md) | | Identifies what kind of target this is. | -| [url()](./puppeteer.target.url.md) | | | -| [worker()](./puppeteer.target.worker.md) | | If the target is not of type "service_worker" or "shared_worker", returns null. | +| Method | Modifiers | Description | +| ------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [asPage()](./puppeteer.target.aspage.md) | | Forcefully creates a page for a target of any type. It is useful if you want to handle a CDP target of type other as a page. If you deal with a regular page target, use [Target.page()](./puppeteer.target.page.md). | +| [browser()](./puppeteer.target.browser.md) | | Get the browser the target belongs to. | +| [browserContext()](./puppeteer.target.browsercontext.md) | | Get the browser context the target belongs to. | +| [createCDPSession()](./puppeteer.target.createcdpsession.md) | | Creates a Chrome Devtools Protocol session attached to the target. | +| [opener()](./puppeteer.target.opener.md) | | Get the target that opened this target. Top-level targets return null. | +| [page()](./puppeteer.target.page.md) | | If the target is not of type "page", "webview" or "background_page", returns null. | +| [type()](./puppeteer.target.type.md) | | Identifies what kind of target this is. | +| [url()](./puppeteer.target.url.md) | | | +| [worker()](./puppeteer.target.worker.md) | | If the target is not of type "service_worker" or "shared_worker", returns null. | diff --git a/packages/puppeteer-core/src/api/Target.ts b/packages/puppeteer-core/src/api/Target.ts index 8ed461fa2fd..67d664dadab 100644 --- a/packages/puppeteer-core/src/api/Target.ts +++ b/packages/puppeteer-core/src/api/Target.ts @@ -65,6 +65,13 @@ export abstract class Target { return null; } + /** + * Forcefully creates a page for a target of any type. It is useful if you + * want to handle a CDP target of type `other` as a page. If you deal with a + * regular page target, use {@link Target.page}. + */ + abstract asPage(): Promise; + abstract url(): string; /** diff --git a/packages/puppeteer-core/src/bidi/Target.ts b/packages/puppeteer-core/src/bidi/Target.ts index 202695a88fe..694074cbfb6 100644 --- a/packages/puppeteer-core/src/bidi/Target.ts +++ b/packages/puppeteer-core/src/bidi/Target.ts @@ -15,6 +15,7 @@ */ import type {CDPSession} from '../api/CDPSession.js'; +import type {Page} from '../api/Page.js'; import {Target, TargetType} from '../api/Target.js'; import {UnsupportedOperation} from '../common/Errors.js'; @@ -38,8 +39,8 @@ export abstract class BidiTarget extends Target { this._browserContext = browserContext; } - override async worker(): Promise { - return null; + override asPage(): Promise { + throw new UnsupportedOperation(); } override browser(): BidiBrowser { diff --git a/packages/puppeteer-core/src/cdp/Target.ts b/packages/puppeteer-core/src/cdp/Target.ts index 11347dde15b..37ef07e4a20 100644 --- a/packages/puppeteer-core/src/cdp/Target.ts +++ b/packages/puppeteer-core/src/cdp/Target.ts @@ -80,6 +80,16 @@ export class CdpTarget extends Target { } } + override async asPage(): Promise { + const session = this._session(); + if (!session) { + return await this.createCDPSession().then(client => { + return CdpPage._create(client, this, false, null); + }); + } + return await CdpPage._create(session, this, false, null); + } + _subtype(): string | undefined { return this.#targetInfo.subtype; } diff --git a/test/src/headful.spec.ts b/test/src/headful.spec.ts index 5b38edd5886..f58ddded85f 100644 --- a/test/src/headful.spec.ts +++ b/test/src/headful.spec.ts @@ -187,6 +187,26 @@ const serviceWorkerExtensionPath = path.join( ).toBe(6); expect(await browser.pages()).toContainEqual(page); }); + it('target.page() should return a DevTools page if asPage is used', async function () { + const {puppeteer} = await getTestState({skipLaunch: true}); + const originalBrowser = await launchBrowser(devtoolsOptions); + + const browserWSEndpoint = originalBrowser.wsEndpoint(); + + const browser = await puppeteer.connect({ + browserWSEndpoint, + }); + const devtoolsPageTarget = await browser.waitForTarget(target => { + return target.type() === 'other'; + }); + const page = (await devtoolsPageTarget.asPage())!; + expect( + await page.evaluate(() => { + return 2 * 3; + }) + ).toBe(6); + expect(await browser.pages()).toContainEqual(page); + }); it('should have default url when launching browser', async function () { const browser = await launchBrowser(extensionOptions); const pages = (await browser.pages()).map((page: {url: () => any}) => {