summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/test/src/cdp
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/test/src/cdp')
-rw-r--r--remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts147
-rw-r--r--remote/test/puppeteer/test/src/cdp/TargetManager.spec.ts96
-rw-r--r--remote/test/puppeteer/test/src/cdp/bfcache.spec.ts65
-rw-r--r--remote/test/puppeteer/test/src/cdp/devtools.spec.ts123
-rw-r--r--remote/test/puppeteer/test/src/cdp/extensions.spec.ts120
-rw-r--r--remote/test/puppeteer/test/src/cdp/prerender.spec.ts181
-rw-r--r--remote/test/puppeteer/test/src/cdp/queryObjects.spec.ts108
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'
+ );
+ });
+});