diff --git a/package.json b/package.json index 6171609f6cdca..7e287cfa88796 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "atest": "playwright test --config=tests/android/playwright.config.ts", "etest": "playwright test --config=tests/electron/playwright.config.ts", "webview2test": "playwright test --config=tests/webview2/playwright.config.ts", + "extensiontest": "playwright test --config=tests/extension/playwright.config.ts", "itest": "playwright test --config=tests/installation/playwright.config.ts", "stest": "playwright test --config=tests/stress/playwright.config.ts", "biditest": "playwright test --config=tests/bidi/playwright.config.ts", diff --git a/tests/extension/extensionTest.ts b/tests/extension/extensionTest.ts new file mode 100644 index 0000000000000..1fecbbe367116 --- /dev/null +++ b/tests/extension/extensionTest.ts @@ -0,0 +1,95 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { baseTest } from '../config/baseTest'; +import { chromium, type BrowserContext } from 'playwright'; +import { expect, type PageTestFixtures, type PageWorkerFixtures } from '../page/pageTestApi'; +import type { TraceViewerFixtures } from '../config/traceViewerFixtures'; +import { traceViewerFixtures } from '../config/traceViewerFixtures'; +export { expect } from '@playwright/test'; +import http from 'node:http'; +import path from 'node:path'; +import { AddressInfo } from 'node:net'; + +export type ExtensionTestFixtures = { + persistentContext: BrowserContext; + relayServer: http.Server; +}; + + +export const extensionTest = baseTest.extend(traceViewerFixtures).extend({ + browserVersion: [({ persistentContext }, use) => use(persistentContext.browser().version()), { scope: 'worker' }], + browserMajorVersion: [({ browserVersion }, use) => use(Number(browserVersion.split('.')[0])), { scope: 'worker' }], + isAndroid: [false, { scope: 'worker' }], + isElectron: [false, { scope: 'worker' }], + electronMajorVersion: [0, { scope: 'worker' }], + isWebView2: [false, { scope: 'worker' }], + isHeadlessShell: [false, { scope: 'worker' }], + + relayServer: [async ({ }, use) => { + const httpServer = http.createServer(); + await new Promise(resolve => httpServer.listen(0, resolve)); + const { CDPRelayServer } = await import('../../../playwright-mcp/src/cdpRelay.ts'); + new CDPRelayServer(httpServer); + await use(httpServer); + httpServer.close(); + }, { scope: 'worker' }], + + persistentContext: [async ({ }, use) => { + const pathToExtension = path.join(__dirname, '../../../playwright-mcp/extension'); + const context = await chromium.launchPersistentContext('', { + executablePath: process.env.CRPATH, + args: [ + `--disable-extensions-except=${pathToExtension}`, + `--load-extension=${pathToExtension}`, + '--enable-features=AllowContentInitiatedDataUrlNavigations', + ], + channel: 'chromium', + }); + context.on('dialog', dialog => { + // Make sure the dialog is not dismissed automatically. + }); + await use(context); + await context.close(); + }, { scope: 'worker' }], + + browser: [async ({ }, use) => { + throw new Error('Not supported in the extension tests'); + }, { scope: 'worker' }], + + context: async ({ }, use) => { + throw new Error('Not supported in the extension tests'); + }, + + page: async ({ persistentContext, relayServer, playwright, server }, use) => { + const page = await persistentContext.newPage(); + const origin = `ws://localhost:${(relayServer.address() as AddressInfo).port}`; + await expect.poll(() => persistentContext.serviceWorkers()).toHaveLength(1); + await page.goto(new URL('/popup.html', persistentContext.serviceWorkers()[0].url()).toString()); + await page.getByRole('textbox', { name: 'Bridge Server URL:' }).clear(); + await page.getByRole('textbox', { name: 'Bridge Server URL:' }).fill(`${origin}/extension`); + await page.getByRole('button', { name: 'Share This Tab' }).click(); + await page.goto('about:blank'); + const browser = await playwright.chromium.connectOverCDP(`${origin}/cdp`); + const pages = browser.contexts()[0].pages(); + const remotePage = pages[pages.length - 1]; + await use(remotePage); + // Disconnect from the tab. + await browser.close(); + // Close the page. + await page.close(); + } +}); diff --git a/tests/extension/playwright.config.ts b/tests/extension/playwright.config.ts new file mode 100644 index 0000000000000..3bee3341a2e1c --- /dev/null +++ b/tests/extension/playwright.config.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { config as loadEnv } from 'dotenv'; +loadEnv({ path: path.join(__dirname, '..', '..', '.env') }); +process.env.PWTEST_UNDER_TEST = '1'; + +import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test'; +import * as path from 'path'; + +process.env.PWPAGE_IMPL = 'extension'; + +const outputDir = path.join(__dirname, '..', '..', 'test-results'); +const testDir = path.join(__dirname, '..'); +const config: Config = { + testDir, + outputDir, + timeout: 30000, + globalTimeout: 5400000, + workers: process.env.CI ? 1 : undefined, + fullyParallel: !process.env.CI, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 0, + reporter: process.env.CI ? [ + ['dot'], + ['json', { outputFile: path.join(outputDir, 'report.json') }], + ['blob', { fileName: `${process.env.PWTEST_BOT_NAME}.zip` }], + ] : 'line', + projects: [], +}; + +const metadata = { + platform: process.platform, + headless: true, + browserName: 'extension', + channel: undefined, + mode: 'default', + video: false, +}; + +config.projects.push({ + name: 'extension', + // Share screenshots with chromium. + snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-chromium{ext}', + use: { + browserName: 'chromium', + }, + testDir: path.join(testDir, 'page'), + metadata, +}); + +export default config; diff --git a/tests/page/pageTest.ts b/tests/page/pageTest.ts index f6647563885f6..7553229703e88 100644 --- a/tests/page/pageTest.ts +++ b/tests/page/pageTest.ts @@ -21,6 +21,7 @@ import { androidTest } from '../android/androidTest'; import { browserTest } from '../config/browserTest'; import { electronTest } from '../electron/electronTest'; import { webView2Test } from '../webview2/webView2Test'; +import { extensionTest } from '../extension/extensionTest'; import type { PageTestFixtures, PageWorkerFixtures } from './pageTestApi'; import type { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures'; import { expect as baseExpect } from '@playwright/test'; @@ -34,6 +35,8 @@ if (process.env.PWPAGE_IMPL === 'electron') impl = electronTest; if (process.env.PWPAGE_IMPL === 'webview2') impl = webView2Test; +if (process.env.PWPAGE_IMPL === 'extension') + impl = extensionTest; export const test = impl;