diff options
Diffstat (limited to 'remote/test/puppeteer/test/src/cdp')
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts | 147 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/TargetManager.spec.ts | 96 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/bfcache.spec.ts | 65 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/devtools.spec.ts | 123 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/extensions.spec.ts | 120 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/prerender.spec.ts | 181 | ||||
-rw-r--r-- | remote/test/puppeteer/test/src/cdp/queryObjects.spec.ts | 108 |
7 files changed, 840 insertions, 0 deletions
diff --git a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts new file mode 100644 index 0000000000..2000c0e435 --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts @@ -0,0 +1,147 @@ +/** + * @license + * Copyright 2018 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import expect from 'expect'; +import type {Target} from 'puppeteer-core/internal/api/Target.js'; +import {isErrorLike} from 'puppeteer-core/internal/util/ErrorLike.js'; + +import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js'; +import {waitEvent} from '../utils.js'; + +describe('Target.createCDPSession', function () { + setupTestBrowserHooks(); + + it('should work', async () => { + const {page} = await getTestState(); + + const client = await page.createCDPSession(); + + await Promise.all([ + client.send('Runtime.enable'), + client.send('Runtime.evaluate', {expression: 'window.foo = "bar"'}), + ]); + const foo = await page.evaluate(() => { + return (globalThis as any).foo; + }); + expect(foo).toBe('bar'); + }); + + it('should not report created targets for custom CDP sessions', async () => { + const {browser} = await getTestState(); + let called = 0; + const handler = async (target: Target) => { + called++; + if (called > 1) { + throw new Error('Too many targets created'); + } + await target.createCDPSession(); + }; + browser.browserContexts()[0]!.on('targetcreated', handler); + await browser.newPage(); + browser.browserContexts()[0]!.off('targetcreated', handler); + }); + + it('should send events', async () => { + const {page, server} = await getTestState(); + + const client = await page.createCDPSession(); + await client.send('Network.enable'); + const events: unknown[] = []; + client.on('Network.requestWillBeSent', event => { + events.push(event); + }); + await Promise.all([ + waitEvent(client, 'Network.requestWillBeSent'), + page.goto(server.EMPTY_PAGE), + ]); + expect(events).toHaveLength(1); + }); + it('should enable and disable domains independently', async () => { + const {page} = await getTestState(); + + const client = await page.createCDPSession(); + await client.send('Runtime.enable'); + await client.send('Debugger.enable'); + // JS coverage enables and then disables Debugger domain. + await page.coverage.startJSCoverage(); + await page.coverage.stopJSCoverage(); + // generate a script in page and wait for the event. + const [event] = await Promise.all([ + waitEvent(client, 'Debugger.scriptParsed'), + page.evaluate('//# sourceURL=foo.js'), + ]); + // expect events to be dispatched. + expect(event.url).toBe('foo.js'); + }); + it('should be able to detach session', async () => { + const {page} = await getTestState(); + + const client = await page.createCDPSession(); + await client.send('Runtime.enable'); + const evalResponse = await client.send('Runtime.evaluate', { + expression: '1 + 2', + returnByValue: true, + }); + expect(evalResponse.result.value).toBe(3); + await client.detach(); + let error!: Error; + try { + await client.send('Runtime.evaluate', { + expression: '3 + 1', + returnByValue: true, + }); + } catch (error_) { + if (isErrorLike(error_)) { + error = error_ as Error; + } + } + expect(error.message).toContain('Session closed.'); + }); + it('should throw nice errors', async () => { + const {page} = await getTestState(); + + const client = await page.createCDPSession(); + const error = await theSourceOfTheProblems().catch(error => { + return error; + }); + expect(error.stack).toContain('theSourceOfTheProblems'); + expect(error.message).toContain('ThisCommand.DoesNotExist'); + + async function theSourceOfTheProblems() { + // @ts-expect-error This fails in TS as it knows that command does not + // exist but we want to have this tests for our users who consume in JS + // not TS. + await client.send('ThisCommand.DoesNotExist'); + } + }); + + 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 () => { + const {page} = await getTestState(); + + const client = await page.createCDPSession(); + expect(client.connection()).toBeTruthy(); + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/TargetManager.spec.ts b/remote/test/puppeteer/test/src/cdp/TargetManager.spec.ts new file mode 100644 index 0000000000..d1f8992530 --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/TargetManager.spec.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2022 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import expect from 'expect'; +import type {CdpBrowser} from 'puppeteer-core/internal/cdp/Browser.js'; + +import {getTestState, launch} from '../mocha-utils.js'; +import {attachFrame} from '../utils.js'; + +describe('TargetManager', () => { + /* We use a special browser for this test as we need the --site-per-process flag */ + let state: Awaited<ReturnType<typeof launch>> & { + browser: CdpBrowser; + }; + + beforeEach(async () => { + const {defaultBrowserOptions} = await getTestState({ + skipLaunch: true, + }); + state = (await launch( + Object.assign({}, defaultBrowserOptions, { + args: (defaultBrowserOptions.args || []).concat([ + '--site-per-process', + '--remote-debugging-port=21222', + '--host-rules=MAP * 127.0.0.1', + ]), + }), + {createPage: false} + )) as Awaited<ReturnType<typeof launch>> & { + browser: CdpBrowser; + }; + }); + + afterEach(async () => { + await state.close(); + }); + + // CDP-specific test. + it('should handle targets', async () => { + const {server, context, browser} = state; + + const targetManager = browser._targetManager(); + expect(targetManager.getAvailableTargets().size).toBe(3); + + expect(await context.pages()).toHaveLength(0); + expect(targetManager.getAvailableTargets().size).toBe(3); + + const page = await context.newPage(); + expect(await context.pages()).toHaveLength(1); + expect(targetManager.getAvailableTargets().size).toBe(5); + + await page.goto(server.EMPTY_PAGE); + expect(await context.pages()).toHaveLength(1); + expect(targetManager.getAvailableTargets().size).toBe(5); + + // attach a local iframe. + let framePromise = page.waitForFrame(frame => { + return frame.url().endsWith('/empty.html'); + }); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await framePromise; + expect(await context.pages()).toHaveLength(1); + expect(targetManager.getAvailableTargets().size).toBe(5); + expect(page.frames()).toHaveLength(2); + + // // attach a remote frame iframe. + framePromise = page.waitForFrame(frame => { + return frame.url() === server.CROSS_PROCESS_PREFIX + '/empty.html'; + }); + await attachFrame( + page, + 'frame2', + server.CROSS_PROCESS_PREFIX + '/empty.html' + ); + await framePromise; + expect(await context.pages()).toHaveLength(1); + expect(targetManager.getAvailableTargets().size).toBe(6); + expect(page.frames()).toHaveLength(3); + + framePromise = page.waitForFrame(frame => { + return frame.url() === server.CROSS_PROCESS_PREFIX + '/empty.html'; + }); + await attachFrame( + page, + 'frame3', + server.CROSS_PROCESS_PREFIX + '/empty.html' + ); + await framePromise; + expect(await context.pages()).toHaveLength(1); + expect(targetManager.getAvailableTargets().size).toBe(7); + expect(page.frames()).toHaveLength(4); + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/bfcache.spec.ts b/remote/test/puppeteer/test/src/cdp/bfcache.spec.ts new file mode 100644 index 0000000000..211f93cd6b --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/bfcache.spec.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2023 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import expect from 'expect'; +import {PageEvent} from 'puppeteer-core'; + +import {launch} from '../mocha-utils.js'; +import {waitEvent} from '../utils.js'; + +describe('BFCache', function () { + it('can navigate to a BFCached page', async () => { + const {httpsServer, page, close} = await launch({ + ignoreHTTPSErrors: true, + }); + + try { + page.setDefaultTimeout(3000); + + await page.goto(httpsServer.PREFIX + '/cached/bfcache/index.html'); + + await Promise.all([page.waitForNavigation(), page.locator('a').click()]); + + expect(page.url()).toContain('target.html'); + + await Promise.all([page.waitForNavigation(), page.goBack()]); + + expect( + await page.evaluate(() => { + return document.body.innerText; + }) + ).toBe('BFCachednext'); + } finally { + await close(); + } + }); + + it('can navigate to a BFCached page containing an OOPIF and a worker', async () => { + const {httpsServer, page, close} = await launch({ + ignoreHTTPSErrors: true, + }); + try { + page.setDefaultTimeout(3000); + const [worker1] = await Promise.all([ + waitEvent(page, PageEvent.WorkerCreated), + page.goto( + httpsServer.PREFIX + '/cached/bfcache/worker-iframe-container.html' + ), + ]); + expect(await worker1.evaluate('1 + 1')).toBe(2); + await Promise.all([page.waitForNavigation(), page.locator('a').click()]); + + const [worker2] = await Promise.all([ + waitEvent(page, PageEvent.WorkerCreated), + page.waitForNavigation(), + page.goBack(), + ]); + expect(await worker2.evaluate('1 + 1')).toBe(2); + } finally { + await close(); + } + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts new file mode 100644 index 0000000000..c158481af2 --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2018 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import expect from 'expect'; +import type {PuppeteerLaunchOptions} from 'puppeteer-core/internal/node/PuppeteerNode.js'; + +import {getTestState, launch} from '../mocha-utils.js'; + +describe('DevTools', function () { + /* These tests fire up an actual browser so let's + * allow a higher timeout + */ + this.timeout(20_000); + + let launchOptions: PuppeteerLaunchOptions & { + devtools: boolean; + }; + const browsers: Array<() => Promise<void>> = []; + + beforeEach(async () => { + const {defaultBrowserOptions} = await getTestState({ + skipLaunch: true, + }); + launchOptions = Object.assign({}, defaultBrowserOptions, { + devtools: true, + }); + }); + + async function launchBrowser(options: typeof launchOptions) { + const {browser, close} = await launch(options, {createContext: false}); + browsers.push(close); + return browser; + } + + afterEach(async () => { + await Promise.all( + browsers.map((close, index) => { + delete browsers[index]; + return close(); + }) + ); + }); + + it('target.page() should return a DevTools page if custom isPageTarget is provided', async function () { + const {puppeteer} = await getTestState({skipLaunch: true}); + const originalBrowser = await launchBrowser(launchOptions); + + const browserWSEndpoint = originalBrowser.wsEndpoint(); + + const browser = await puppeteer.connect({ + browserWSEndpoint, + _isPageTarget(target) { + return ( + target.type() === 'other' && target.url().startsWith('devtools://') + ); + }, + }); + const devtoolsPageTarget = await browser.waitForTarget(target => { + return target.type() === 'other'; + }); + const page = (await devtoolsPageTarget.page())!; + expect( + await page.evaluate(() => { + return 2 * 3; + }) + ).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(launchOptions); + + 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 open devtools when "devtools: true" option is given', async () => { + const browser = await launchBrowser( + Object.assign({devtools: true}, launchOptions) + ); + const context = await browser.createIncognitoBrowserContext(); + await Promise.all([ + context.newPage(), + browser.waitForTarget((target: {url: () => string | string[]}) => { + return target.url().includes('devtools://'); + }), + ]); + await browser.close(); + }); + it('should expose DevTools as a page', async () => { + const browser = await launchBrowser( + Object.assign({devtools: true}, launchOptions) + ); + const context = await browser.createIncognitoBrowserContext(); + const [target] = await Promise.all([ + browser.waitForTarget((target: {url: () => string | string[]}) => { + return target.url().includes('devtools://'); + }), + context.newPage(), + ]); + const page = await target.page(); + await page!.waitForFunction(() => { + // @ts-expect-error wrong context. + return Boolean(DevToolsAPI); + }); + await browser.close(); + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/extensions.spec.ts b/remote/test/puppeteer/test/src/cdp/extensions.spec.ts new file mode 100644 index 0000000000..6db9f931ad --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/extensions.spec.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright 2018 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import path from 'path'; + +import expect from 'expect'; +import type {PuppeteerLaunchOptions} from 'puppeteer-core/internal/node/PuppeteerNode.js'; + +import {getTestState, launch} from '../mocha-utils.js'; + +const extensionPath = path.join( + __dirname, + '..', + '..', + 'assets', + 'simple-extension' +); +const serviceWorkerExtensionPath = path.join( + __dirname, + '..', + '..', + 'assets', + 'serviceworkers', + 'extension' +); + +describe('extensions', function () { + /* These tests fire up an actual browser so let's + * allow a higher timeout + */ + this.timeout(20_000); + + let extensionOptions: PuppeteerLaunchOptions & { + args: string[]; + }; + const browsers: Array<() => Promise<void>> = []; + + beforeEach(async () => { + const {defaultBrowserOptions} = await getTestState({ + skipLaunch: true, + }); + + extensionOptions = Object.assign({}, defaultBrowserOptions, { + args: [ + `--disable-extensions-except=${extensionPath}`, + `--load-extension=${extensionPath}`, + ], + }); + }); + + async function launchBrowser(options: typeof extensionOptions) { + const {browser, close} = await launch(options, {createContext: false}); + browsers.push(close); + return browser; + } + + afterEach(async () => { + await Promise.all( + browsers.map((close, index) => { + delete browsers[index]; + return close(); + }) + ); + }); + + it('background_page target type should be available', async () => { + const browserWithExtension = await launchBrowser(extensionOptions); + const page = await browserWithExtension.newPage(); + const backgroundPageTarget = await browserWithExtension.waitForTarget( + target => { + return target.type() === 'background_page'; + } + ); + await page.close(); + await browserWithExtension.close(); + expect(backgroundPageTarget).toBeTruthy(); + }); + + it('service_worker target type should be available', async () => { + const browserWithExtension = await launchBrowser({ + args: [ + `--disable-extensions-except=${serviceWorkerExtensionPath}`, + `--load-extension=${serviceWorkerExtensionPath}`, + ], + }); + const page = await browserWithExtension.newPage(); + const serviceWorkerTarget = await browserWithExtension.waitForTarget( + target => { + return target.type() === 'service_worker'; + } + ); + await page.close(); + await browserWithExtension.close(); + expect(serviceWorkerTarget).toBeTruthy(); + }); + + it('target.page() should return a background_page', async function () { + const browserWithExtension = await launchBrowser(extensionOptions); + const backgroundPageTarget = await browserWithExtension.waitForTarget( + target => { + return target.type() === 'background_page'; + } + ); + const page = (await backgroundPageTarget.page())!; + expect( + await page.evaluate(() => { + return 2 * 3; + }) + ).toBe(6); + expect( + await page.evaluate(() => { + return (globalThis as any).MAGIC; + }) + ).toBe(42); + await browserWithExtension.close(); + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/prerender.spec.ts b/remote/test/puppeteer/test/src/cdp/prerender.spec.ts new file mode 100644 index 0000000000..4e0fb30da9 --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/prerender.spec.ts @@ -0,0 +1,181 @@ +/** + * @license + * Copyright 2023 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import {statSync} from 'fs'; + +import expect from 'expect'; + +import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js'; +import {getUniqueVideoFilePlaceholder} from '../utils.js'; + +describe('Prerender', function () { + setupTestBrowserHooks(); + + it('can navigate to a prerendered page via input', async () => { + const {page, server} = await getTestState(); + await page.goto(server.PREFIX + '/prerender/index.html'); + + using button = await page.waitForSelector('button'); + await button?.click(); + + using link = await page.waitForSelector('a'); + await Promise.all([page.waitForNavigation(), link?.click()]); + expect( + await page.evaluate(() => { + return document.body.innerText; + }) + ).toBe('target'); + }); + + it('can navigate to a prerendered page via Puppeteer', async () => { + const {page, server} = await getTestState(); + await page.goto(server.PREFIX + '/prerender/index.html'); + + using button = await page.waitForSelector('button'); + await button?.click(); + + await page.goto(server.PREFIX + '/prerender/target.html'); + expect( + await page.evaluate(() => { + return document.body.innerText; + }) + ).toBe('target'); + }); + + describe('via frame', () => { + it('can navigate to a prerendered page via input', async () => { + const {page, server} = await getTestState(); + await page.goto(server.PREFIX + '/prerender/index.html'); + + using button = await page.waitForSelector('button'); + await button?.click(); + + const mainFrame = page.mainFrame(); + using link = await mainFrame.waitForSelector('a'); + await Promise.all([mainFrame.waitForNavigation(), link?.click()]); + expect(mainFrame).toBe(page.mainFrame()); + expect( + await mainFrame.evaluate(() => { + return document.body.innerText; + }) + ).toBe('target'); + expect(mainFrame).toBe(page.mainFrame()); + }); + + it('can navigate to a prerendered page via Puppeteer', async () => { + const {page, server} = await getTestState(); + await page.goto(server.PREFIX + '/prerender/index.html'); + + using button = await page.waitForSelector('button'); + await button?.click(); + + const mainFrame = page.mainFrame(); + await mainFrame.goto(server.PREFIX + '/prerender/target.html'); + expect( + await mainFrame.evaluate(() => { + return document.body.innerText; + }) + ).toBe('target'); + expect(mainFrame).toBe(page.mainFrame()); + }); + }); + + it('can screencast', async () => { + using file = getUniqueVideoFilePlaceholder(); + + const {page, server} = await getTestState(); + + const recorder = await page.screencast({ + path: file.filename, + scale: 0.5, + crop: {width: 100, height: 100, x: 0, y: 0}, + speed: 0.5, + }); + + await page.goto(server.PREFIX + '/prerender/index.html'); + + using button = await page.waitForSelector('button'); + await button?.click(); + + using link = await page.locator('a').waitHandle(); + await Promise.all([page.waitForNavigation(), link.click()]); + using input = await page.locator('input').waitHandle(); + await input.type('ab', {delay: 100}); + + await recorder.stop(); + + expect(statSync(file.filename).size).toBeGreaterThan(0); + }); + + describe('with network requests', () => { + it('can receive requests from the prerendered page', async () => { + const {page, server} = await getTestState(); + + const urls: string[] = []; + page.on('request', request => { + urls.push(request.url()); + }); + + await page.goto(server.PREFIX + '/prerender/index.html'); + using button = await page.waitForSelector('button'); + await button?.click(); + const mainFrame = page.mainFrame(); + using link = await mainFrame.waitForSelector('a'); + await Promise.all([mainFrame.waitForNavigation(), link?.click()]); + expect(mainFrame).toBe(page.mainFrame()); + expect( + await mainFrame.evaluate(() => { + return document.body.innerText; + }) + ).toBe('target'); + expect(mainFrame).toBe(page.mainFrame()); + expect( + urls.find(url => { + return url.endsWith('prerender/target.html'); + }) + ).toBeTruthy(); + expect( + urls.find(url => { + return url.includes('prerender/index.html'); + }) + ).toBeTruthy(); + expect( + urls.find(url => { + return url.includes('prerender/target.html?fromPrerendered'); + }) + ).toBeTruthy(); + }); + }); + + describe('with emulation', () => { + it('can configure viewport for prerendered pages', async () => { + const {page, server} = await getTestState(); + await page.setViewport({ + width: 300, + height: 400, + }); + await page.goto(server.PREFIX + '/prerender/index.html'); + using button = await page.waitForSelector('button'); + await button?.click(); + using link = await page.waitForSelector('a'); + await Promise.all([page.waitForNavigation(), link?.click()]); + const result = await page.evaluate(() => { + return { + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, + dpr: window.devicePixelRatio, + }; + }); + expect({ + width: result.width, + height: result.height, + }).toStrictEqual({ + width: 300 * result.dpr, + height: 400 * result.dpr, + }); + }); + }); +}); diff --git a/remote/test/puppeteer/test/src/cdp/queryObjects.spec.ts b/remote/test/puppeteer/test/src/cdp/queryObjects.spec.ts new file mode 100644 index 0000000000..405303fb6b --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/queryObjects.spec.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2017 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +import expect from 'expect'; + +import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js'; + +describe('page.queryObjects', function () { + setupTestBrowserHooks(); + + it('should work', async () => { + const {page} = await getTestState(); + + // Create a custom class + using classHandle = await page.evaluateHandle(() => { + return class CustomClass {}; + }); + + // Create an instance. + await page.evaluate(CustomClass => { + // @ts-expect-error: Different context. + self.customClass = new CustomClass(); + }, classHandle); + + // Validate only one has been added. + using prototypeHandle = await page.evaluateHandle(CustomClass => { + return CustomClass.prototype; + }, classHandle); + using objectsHandle = await page.queryObjects(prototypeHandle); + await expect( + page.evaluate(objects => { + return objects.length; + }, objectsHandle) + ).resolves.toBe(1); + + // Check that instances. + await expect( + page.evaluate(objects => { + // @ts-expect-error: Different context. + return objects[0] === self.customClass; + }, objectsHandle) + ).resolves.toBeTruthy(); + }); + it('should work for non-trivial page', async () => { + const {page, server} = await getTestState(); + await page.goto(server.EMPTY_PAGE); + + // Create a custom class + using classHandle = await page.evaluateHandle(() => { + return class CustomClass {}; + }); + + // Create an instance. + await page.evaluate(CustomClass => { + // @ts-expect-error: Different context. + self.customClass = new CustomClass(); + }, classHandle); + + // Validate only one has been added. + using prototypeHandle = await page.evaluateHandle(CustomClass => { + return CustomClass.prototype; + }, classHandle); + using objectsHandle = await page.queryObjects(prototypeHandle); + await expect( + page.evaluate(objects => { + return objects.length; + }, objectsHandle) + ).resolves.toBe(1); + + // Check that instances. + await expect( + page.evaluate(objects => { + // @ts-expect-error: Different context. + return objects[0] === self.customClass; + }, objectsHandle) + ).resolves.toBeTruthy(); + }); + it('should fail for disposed handles', async () => { + const {page} = await getTestState(); + + using prototypeHandle = await page.evaluateHandle(() => { + return HTMLBodyElement.prototype; + }); + // We want to dispose early. + await prototypeHandle.dispose(); + let error!: Error; + await page.queryObjects(prototypeHandle).catch(error_ => { + return (error = error_); + }); + expect(error.message).toBe('Prototype JSHandle is disposed!'); + }); + it('should fail primitive values as prototypes', async () => { + const {page} = await getTestState(); + + using prototypeHandle = await page.evaluateHandle(() => { + return 42; + }); + let error!: Error; + await page.queryObjects(prototypeHandle).catch(error_ => { + return (error = error_); + }); + expect(error.message).toBe( + 'Prototype JSHandle must not be referencing primitive value' + ); + }); +}); |