diff --git a/installation-tests/driver-client.js b/installation-tests/driver-client.js index 1d586d260a..dba12d04d8 100644 --- a/installation-tests/driver-client.js +++ b/installation-tests/driver-client.js @@ -17,7 +17,7 @@ const { start } = require('./node_modules/playwright-core/lib/outofprocess'); (async () => { - const playwright = await start(); + const { playwright, stop } = await start(); console.log(`driver PID=${playwright.driverProcess.pid}`); for (const browserType of ['chromium', 'firefox', 'webkit']) { try { @@ -33,7 +33,7 @@ const { start } = require('./node_modules/playwright-core/lib/outofprocess'); process.exit(1); } } - await playwright.stop(); + await stop(); console.log(`driver SUCCESS`); })().catch(err => { console.error(err); diff --git a/packages/playwright-core/src/outofprocess.ts b/packages/playwright-core/src/outofprocess.ts index 69d831f528..89cfdcbd52 100644 --- a/packages/playwright-core/src/outofprocess.ts +++ b/packages/playwright-core/src/outofprocess.ts @@ -20,12 +20,11 @@ import { Playwright } from './client/playwright'; import * as childProcess from 'child_process'; import * as path from 'path'; -export async function start(env: any = {}) { +export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise }> { const client = new PlaywrightClient(env); const playwright = await client._playwright; - (playwright as any).stop = () => client.stop(); (playwright as any).driverProcess = client._driverProcess; - return playwright; + return { playwright, stop: () => client.stop() }; } class PlaywrightClient { diff --git a/tests/android/androidTest.ts b/tests/android/androidTest.ts index 72171fa21c..15bce22c28 100644 --- a/tests/android/androidTest.ts +++ b/tests/android/androidTest.ts @@ -15,16 +15,17 @@ */ import type { AndroidDevice, BrowserContext } from 'playwright-core'; -import { CommonWorkerFixtures, baseTest } from '../config/baseTest'; -import type { Fixtures } from '@playwright/test'; +import type { Fixtures, PlaywrightWorkerOptions } from '@playwright/test'; import { PageTestFixtures } from '../page/pageTest'; +import { TestModeWorkerFixtures } from '../config/testModeFixtures'; +import { browserTest } from '../config/browserTest'; export { expect } from '@playwright/test'; type AndroidWorkerFixtures = { androidDevice: AndroidDevice; }; -export const androidFixtures: Fixtures = { +export const androidFixtures: Fixtures = { androidDevice: [ async ({ playwright }, run) => { const device = (await playwright._android.devices())[0]; await device.shell('am force-stop org.chromium.webview_shell'); @@ -67,4 +68,4 @@ export const androidFixtures: Fixtures(androidFixtures as any); +export const androidTest = browserTest.extend(androidFixtures as any); diff --git a/tests/browsertype-launch-selenium.spec.ts b/tests/browsertype-launch-selenium.spec.ts index 68efaacb3b..667d375431 100644 --- a/tests/browsertype-launch-selenium.spec.ts +++ b/tests/browsertype-launch-selenium.spec.ts @@ -118,7 +118,7 @@ test('selenium grid 3.141.59 standalone chromium through driver', async ({ brows }); await waitForPort(port); - const pw = await start({ + const { playwright: pw, stop } = await start({ SELENIUM_REMOTE_URL: `http://localhost:${port}/wd/hub`, }); const browser = await pw.chromium.launch(browserOptions); @@ -128,7 +128,7 @@ test('selenium grid 3.141.59 standalone chromium through driver', async ({ brows await expect(page).toHaveTitle('Hello world'); // Note: it is important to stop the driver without explicitly closing the browser. // It should terminate selenium session in this case. - await pw.stop(); + await stop(); expect(grid.output).toContain('Starting ChromeDriver'); expect(grid.output).toContain('Started new session'); diff --git a/tests/config/android.config.ts b/tests/config/android.config.ts index ec20d19735..c690b3531a 100644 --- a/tests/config/android.config.ts +++ b/tests/config/android.config.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import type { Config } from '@playwright/test'; +import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test'; import * as path from 'path'; import { test as pageTest } from '../page/pageTest'; import { androidFixtures } from '../android/androidTest'; -import { PlaywrightOptionsEx } from './browserTest'; -import { CommonOptions } from './baseTest'; +import { ServerWorkerOptions } from './serverFixtures'; +import { playwrightFixtures } from './browserTest'; const outputDir = path.join(__dirname, '..', '..', 'test-results'); const testDir = path.join(__dirname, '..'); -const config: Config = { +const config: Config = { testDir, outputDir, timeout: 120000, @@ -52,7 +52,6 @@ config.projects.push({ name: 'android', use: { loopback: '10.0.2.2', - mode: 'default', browserName: 'chromium', }, testDir: path.join(testDir, 'android'), @@ -63,11 +62,10 @@ config.projects.push({ name: 'android', use: { loopback: '10.0.2.2', - mode: 'default', browserName: 'chromium', }, testDir: path.join(testDir, 'page'), - define: { test: pageTest, fixtures: androidFixtures }, + define: { test: pageTest, fixtures: { ...playwrightFixtures, ...androidFixtures } }, metadata, }); diff --git a/tests/config/baseTest.ts b/tests/config/baseTest.ts index 7a61a8f8b0..321fc01c65 100644 --- a/tests/config/baseTest.ts +++ b/tests/config/baseTest.ts @@ -14,118 +14,16 @@ * limitations under the License. */ -import { Fixtures, VideoMode, _baseTest } from '@playwright/test'; -import * as path from 'path'; -import * as fs from 'fs'; -import { installCoverageHooks } from './coverage'; -import { start } from '../../packages/playwright-core/lib/outofprocess'; -import { GridClient } from 'playwright-core/lib/grid/gridClient'; -import type { LaunchOptions, ViewportSize } from 'playwright-core'; -import { commonFixtures, CommonFixtures, serverFixtures, ServerFixtures, ServerOptions } from './commonFixtures'; +import { _baseTest } from '@playwright/test'; +import { commonFixtures, CommonFixtures } from './commonFixtures'; +import { serverFixtures, ServerFixtures, ServerWorkerOptions } from './serverFixtures'; +import { coverageFixtures, CoverageWorkerOptions } from './coverageFixtures'; +import { platformFixtures, PlatformWorkerFixtures } from './platformFixtures'; +import { testModeFixtures, TestModeWorkerFixtures } from './testModeFixtures'; -export type BrowserName = 'chromium' | 'firefox' | 'webkit'; -type Mode = 'default' | 'driver' | 'service'; -type BaseOptions = { - mode: Mode; - browserName: BrowserName; - channel: LaunchOptions['channel']; - video: VideoMode | { mode: VideoMode, size: ViewportSize }; - trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace'; - headless: boolean | undefined; -}; -type BaseFixtures = { - platform: 'win32' | 'darwin' | 'linux'; - playwright: typeof import('playwright-core'); - toImpl: (rpcObject: any) => any; - isWindows: boolean; - isMac: boolean; - isLinux: boolean; -}; - -class DriverMode { - private _playwrightObject: any; - - async setup(workerIndex: number) { - this._playwrightObject = await start(); - return this._playwrightObject; - } - - async teardown() { - await this._playwrightObject.stop(); - } -} - -class ServiceMode { - private _gridClient: GridClient; - - async setup(workerIndex: number) { - this._gridClient = await GridClient.connect('http://localhost:3333'); - return this._gridClient.playwright(); - } - - async teardown() { - await this._gridClient.close(); - } -} - -class DefaultMode { - async setup(workerIndex: number) { - return require('playwright-core'); - } - - async teardown() { - } -} - -const baseFixtures: Fixtures<{}, BaseOptions & BaseFixtures> = { - mode: [ 'default', { scope: 'worker' } ], - browserName: [ 'chromium' , { scope: 'worker' } ], - channel: [ undefined, { scope: 'worker' } ], - video: [ undefined, { scope: 'worker' } ], - trace: [ undefined, { scope: 'worker' } ], - headless: [ undefined, { scope: 'worker' } ], - platform: [ process.platform as 'win32' | 'darwin' | 'linux', { scope: 'worker' } ], - playwright: [ async ({ mode }, run, workerInfo) => { - const modeImpl = { - default: new DefaultMode(), - service: new ServiceMode(), - driver: new DriverMode(), - }[mode]; - require('playwright-core/lib/utils/utils').setUnderTest(); - const playwright = await modeImpl.setup(workerInfo.workerIndex); - await run(playwright); - await modeImpl.teardown(); - }, { scope: 'worker' } ], - toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ], - isWindows: [ process.platform === 'win32', { scope: 'worker' } ], - isMac: [ process.platform === 'darwin', { scope: 'worker' } ], - isLinux: [ process.platform === 'linux', { scope: 'worker' } ], -}; - -type CoverageOptions = { - coverageName?: string; -}; - -const coverageFixtures: Fixtures<{}, CoverageOptions & { __collectCoverage: void }> = { - coverageName: [ undefined, { scope: 'worker' } ], - - __collectCoverage: [ async ({ coverageName }, run, workerInfo) => { - if (!coverageName) { - await run(); - return; - } - - const { coverage, uninstall } = installCoverageHooks(coverageName); - await run(); - uninstall(); - const coveragePath = path.join(__dirname, '..', 'coverage-report', workerInfo.workerIndex + '.json'); - const coverageJSON = Array.from(coverage.keys()).filter(key => coverage.get(key)); - await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true }); - await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8'); - }, { scope: 'worker', auto: true } ], -}; - -export type CommonOptions = BaseOptions & ServerOptions & CoverageOptions; -export type CommonWorkerFixtures = CommonOptions & BaseFixtures; - -export const baseTest = _baseTest.extend(commonFixtures).extend<{}, CoverageOptions>(coverageFixtures).extend(serverFixtures as any).extend<{}, BaseOptions & BaseFixtures>(baseFixtures); +export const baseTest = _baseTest + .extend<{}, CoverageWorkerOptions>(coverageFixtures) + .extend<{}, PlatformWorkerFixtures>(platformFixtures) + .extend<{}, TestModeWorkerFixtures>(testModeFixtures) + .extend(commonFixtures) + .extend(serverFixtures as any); diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 9034627b3f..2397634727 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -22,11 +22,14 @@ import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; import { RemoteServer, RemoteServerOptions } from './remoteServer'; -import { baseTest, CommonWorkerFixtures } from './baseTest'; +import { baseTest } from './baseTest'; import { CommonFixtures } from './commonFixtures'; import type { ParsedStackTrace } from 'playwright-core/lib/utils/stackTrace'; +import { DefaultTestMode, DriverTestMode, ServiceTestMode } from './testMode'; +import { TestModeWorkerFixtures } from './testModeFixtures'; export type PlaywrightWorkerFixtures = { + playwright: typeof import('playwright-core'); _browserType: BrowserType; _browserOptions: LaunchOptions; browserType: BrowserType; @@ -34,6 +37,7 @@ export type PlaywrightWorkerFixtures = { browser: Browser; browserVersion: string; _reuseBrowserContext: ReuseBrowserContextStorage; + toImpl: (rpcObject: any) => any; }; type PlaywrightTestFixtures = { @@ -45,11 +49,24 @@ type PlaywrightTestFixtures = { context: BrowserContext; page: Page; }; -export type PlaywrightOptionsEx = PlaywrightWorkerOptions & PlaywrightTestOptions; -export const playwrightFixtures: Fixtures = { +export const playwrightFixtures: Fixtures = { hasTouch: undefined, + playwright: [ async ({ mode }, run) => { + const testMode = { + default: new DefaultTestMode(), + service: new ServiceTestMode(), + driver: new DriverTestMode(), + }[mode]; + require('playwright-core/lib/utils/utils').setUnderTest(); + const playwright = await testMode.setup(); + await run(playwright); + await testMode.teardown(); + }, { scope: 'worker' } ], + + toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ], + _browserType: [browserTypeWorkerFixture, { scope: 'worker' } ], _browserOptions: [browserOptionsWorkerFixture, { scope: 'worker' } ], @@ -148,7 +165,7 @@ export const playwrightFixtures: Fixtures { const videos = context.pages().map(p => p.video()).filter(Boolean); - if (trace && !contexts.get(context)!.closed) { + if (trace === 'on' && !contexts.get(context)!.closed) { const tracePath = testInfo.outputPath('trace.zip'); await context.tracing.stop({ path: tracePath }); testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' }); @@ -181,6 +198,12 @@ export const playwrightFixtures: Fixtures(playwrightFixtures); diff --git a/tests/config/commonFixtures.ts b/tests/config/commonFixtures.ts index 0876409e86..3c6251ba1b 100644 --- a/tests/config/commonFixtures.ts +++ b/tests/config/commonFixtures.ts @@ -17,10 +17,6 @@ import type { Fixtures } from '@playwright/test'; import { ChildProcess, execSync, spawn } from 'child_process'; import net from 'net'; -import path from 'path'; -import socks from 'socksv5'; -import { TestServer } from '../../utils/testserver'; -import { TestProxy } from './proxy'; type TestChildParams = { command: string[], @@ -150,93 +146,3 @@ export const commonFixtures: Fixtures = { token.canceled = true; }, }; - -export type ServerOptions = { - loopback?: string; -}; -export type ServerFixtures = { - server: TestServer; - httpsServer: TestServer; - socksPort: number; - proxyServer: TestProxy; - asset: (p: string) => string; -}; - -export type ServersInternal = ServerFixtures & { socksServer: socks.SocksServer }; -export const serverFixtures: Fixtures = { - loopback: [ undefined, { scope: 'worker' } ], - __servers: [ async ({ loopback }, run, workerInfo) => { - const assetsPath = path.join(__dirname, '..', 'assets'); - const cachedPath = path.join(__dirname, '..', 'assets', 'cached'); - - const port = 8907 + workerInfo.workerIndex * 4; - const server = await TestServer.create(assetsPath, port, loopback); - server.enableHTTPCache(cachedPath); - - const httpsPort = port + 1; - const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort, loopback); - httpsServer.enableHTTPCache(cachedPath); - - const socksServer = socks.createServer((info, accept, deny) => { - const socket = accept(true); - if (socket) { - // Catch and ignore ECONNRESET errors. - socket.on('error', () => {}); - const body = 'Served by the SOCKS proxy'; - socket.end([ - 'HTTP/1.1 200 OK', - 'Connection: close', - 'Content-Type: text/html', - 'Content-Length: ' + Buffer.byteLength(body), - '', - body - ].join('\r\n')); - } - }); - const socksPort = port + 2; - socksServer.listen(socksPort, 'localhost'); - socksServer.useAuth(socks.auth.None()); - - const proxyPort = port + 3; - const proxyServer = await TestProxy.create(proxyPort); - - await run({ - asset: (p: string) => path.join(__dirname, '..', 'assets', ...p.split('/')), - server, - httpsServer, - socksPort, - proxyServer, - socksServer, - }); - - await Promise.all([ - server.stop(), - httpsServer.stop(), - socksServer.close(), - proxyServer.stop(), - ]); - }, { scope: 'worker' } ], - - server: async ({ __servers }, run) => { - __servers.server.reset(); - await run(__servers.server); - }, - - httpsServer: async ({ __servers }, run) => { - __servers.httpsServer.reset(); - await run(__servers.httpsServer); - }, - - socksPort: async ({ __servers }, run) => { - await run(__servers.socksPort); - }, - - proxyServer: async ({ __servers }, run) => { - __servers.proxyServer.reset(); - await run(__servers.proxyServer); - }, - - asset: async ({ __servers }, run) => { - await run(__servers.asset); - }, -}; diff --git a/tests/config/coverage.js b/tests/config/coverage.js index 159041662a..6c2d46fc7f 100644 --- a/tests/config/coverage.js +++ b/tests/config/coverage.js @@ -15,9 +15,6 @@ * limitations under the License. */ -const path = require('path'); -const fs = require('fs'); - /** * @param {Map} apiCoverage * @param {Object} events diff --git a/tests/config/coverageFixtures.ts b/tests/config/coverageFixtures.ts new file mode 100644 index 0000000000..920ab55e47 --- /dev/null +++ b/tests/config/coverageFixtures.ts @@ -0,0 +1,43 @@ +/** + * 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 { Fixtures } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { installCoverageHooks } from './coverage'; + +export type CoverageWorkerOptions = { + coverageName?: string; +}; + +export const coverageFixtures: Fixtures<{}, CoverageWorkerOptions & { __collectCoverage: void }> = { + coverageName: [ undefined, { scope: 'worker' } ], + + __collectCoverage: [ async ({ coverageName }, run, workerInfo) => { + if (!coverageName) { + await run(); + return; + } + + const { coverage, uninstall } = installCoverageHooks(coverageName); + await run(); + uninstall(); + const coveragePath = path.join(__dirname, '..', 'coverage-report', workerInfo.workerIndex + '.json'); + const coverageJSON = Array.from(coverage.keys()).filter(key => coverage.get(key)); + await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true }); + await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8'); + }, { scope: 'worker', auto: true } ], +}; diff --git a/tests/config/default.config.ts b/tests/config/default.config.ts index e21a89161b..566642e958 100644 --- a/tests/config/default.config.ts +++ b/tests/config/default.config.ts @@ -18,7 +18,10 @@ import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@pl import * as path from 'path'; import { playwrightFixtures } from './browserTest'; import { test as pageTest } from '../page/pageTest'; -import { BrowserName, CommonOptions } from './baseTest'; +import { TestModeWorkerFixtures } from './testModeFixtures'; +import { CoverageWorkerOptions } from './coverageFixtures'; + +type BrowserName = 'chromium' | 'firefox' | 'webkit'; const getExecutablePath = (browserName: BrowserName) => { if (browserName === 'chromium' && process.env.CRPATH) @@ -46,7 +49,7 @@ const trace = !!process.env.PWTEST_TRACE; const outputDir = path.join(__dirname, '..', '..', 'test-results'); const testDir = path.join(__dirname, '..'); -const config: Config = { +const config: Config = { testDir, outputDir, timeout: video ? 60000 : 30000, diff --git a/tests/config/electron.config.ts b/tests/config/electron.config.ts index 5c2cace8e7..4eac391295 100644 --- a/tests/config/electron.config.ts +++ b/tests/config/electron.config.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import type { Config } from '@playwright/test'; +import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test'; import * as path from 'path'; import { electronFixtures } from '../electron/electronTest'; import { test as pageTest } from '../page/pageTest'; -import { PlaywrightOptionsEx } from './browserTest'; -import { CommonOptions } from './baseTest'; +import { playwrightFixtures } from './browserTest'; +import { CoverageWorkerOptions } from './coverageFixtures'; const outputDir = path.join(__dirname, '..', '..', 'test-results'); const testDir = path.join(__dirname, '..'); -const config: Config = { +const config: Config = { testDir, outputDir, timeout: 30000, @@ -51,7 +51,6 @@ const metadata = { config.projects.push({ name: 'chromium', // We use 'chromium' here to share screenshots with chromium. use: { - mode: 'default', browserName: 'chromium', coverageName: 'electron', }, @@ -62,12 +61,11 @@ config.projects.push({ config.projects.push({ name: 'chromium', // We use 'chromium' here to share screenshots with chromium. use: { - mode: 'default', browserName: 'chromium', coverageName: 'electron', }, testDir: path.join(testDir, 'page'), - define: { test: pageTest, fixtures: electronFixtures }, + define: { test: pageTest, fixtures: { ...playwrightFixtures, ...electronFixtures } }, metadata, }); diff --git a/tests/config/platformFixtures.ts b/tests/config/platformFixtures.ts new file mode 100644 index 0000000000..2d2b01cf3a --- /dev/null +++ b/tests/config/platformFixtures.ts @@ -0,0 +1,31 @@ +/** + * 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 { Fixtures } from '@playwright/test'; + +export type PlatformWorkerFixtures = { + platform: 'win32' | 'darwin' | 'linux'; + isWindows: boolean; + isMac: boolean; + isLinux: boolean; +}; + +export const platformFixtures: Fixtures<{}, PlatformWorkerFixtures> = { + platform: [ process.platform as 'win32' | 'darwin' | 'linux', { scope: 'worker' } ], + isWindows: [ process.platform === 'win32', { scope: 'worker' } ], + isMac: [ process.platform === 'darwin', { scope: 'worker' } ], + isLinux: [ process.platform === 'linux', { scope: 'worker' } ], +}; diff --git a/tests/config/serverFixtures.ts b/tests/config/serverFixtures.ts new file mode 100644 index 0000000000..4ad5d9216b --- /dev/null +++ b/tests/config/serverFixtures.ts @@ -0,0 +1,112 @@ +/** + * 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 type { Fixtures } from '@playwright/test'; +import path from 'path'; +import socks from 'socksv5'; +import { TestServer } from '../../utils/testserver'; +import { TestProxy } from './proxy'; + +export type ServerWorkerOptions = { + loopback?: string; +}; + +export type ServerFixtures = { + server: TestServer; + httpsServer: TestServer; + socksPort: number; + proxyServer: TestProxy; + asset: (p: string) => string; +}; + +export type ServersInternal = ServerFixtures & { socksServer: socks.SocksServer }; +export const serverFixtures: Fixtures = { + loopback: [ undefined, { scope: 'worker' } ], + __servers: [ async ({ loopback }, run, workerInfo) => { + const assetsPath = path.join(__dirname, '..', 'assets'); + const cachedPath = path.join(__dirname, '..', 'assets', 'cached'); + + const port = 8907 + workerInfo.workerIndex * 4; + const server = await TestServer.create(assetsPath, port, loopback); + server.enableHTTPCache(cachedPath); + + const httpsPort = port + 1; + const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort, loopback); + httpsServer.enableHTTPCache(cachedPath); + + const socksServer = socks.createServer((info, accept, deny) => { + const socket = accept(true); + if (socket) { + // Catch and ignore ECONNRESET errors. + socket.on('error', () => {}); + const body = 'Served by the SOCKS proxy'; + socket.end([ + 'HTTP/1.1 200 OK', + 'Connection: close', + 'Content-Type: text/html', + 'Content-Length: ' + Buffer.byteLength(body), + '', + body + ].join('\r\n')); + } + }); + const socksPort = port + 2; + socksServer.listen(socksPort, 'localhost'); + socksServer.useAuth(socks.auth.None()); + + const proxyPort = port + 3; + const proxyServer = await TestProxy.create(proxyPort); + + await run({ + asset: (p: string) => path.join(__dirname, '..', 'assets', ...p.split('/')), + server, + httpsServer, + socksPort, + proxyServer, + socksServer, + }); + + await Promise.all([ + server.stop(), + httpsServer.stop(), + socksServer.close(), + proxyServer.stop(), + ]); + }, { scope: 'worker' } ], + + server: async ({ __servers }, run) => { + __servers.server.reset(); + await run(__servers.server); + }, + + httpsServer: async ({ __servers }, run) => { + __servers.httpsServer.reset(); + await run(__servers.httpsServer); + }, + + socksPort: async ({ __servers }, run) => { + await run(__servers.socksPort); + }, + + proxyServer: async ({ __servers }, run) => { + __servers.proxyServer.reset(); + await run(__servers.proxyServer); + }, + + asset: async ({ __servers }, run) => { + await run(__servers.asset); + }, +}; diff --git a/tests/config/testMode.ts b/tests/config/testMode.ts new file mode 100644 index 0000000000..200ef7f9be --- /dev/null +++ b/tests/config/testMode.ts @@ -0,0 +1,61 @@ +/** + * 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 { GridClient } from '../../packages/playwright-core/lib/grid/gridClient'; +import { start } from '../../packages/playwright-core/lib/outofprocess'; +import { Playwright } from '../../packages/playwright-core/lib/client/playwright'; + +export type TestModeName = 'default' | 'driver' | 'service'; + +interface TestMode { + setup(): Promise; + teardown(): Promise; +} + +export class DriverTestMode implements TestMode { + private _impl: { playwright: Playwright; stop: () => Promise; }; + + async setup() { + this._impl = await start(); + return this._impl.playwright; + } + + async teardown() { + await this._impl.stop(); + } +} + +export class ServiceTestMode implements TestMode { + private _gridClient: GridClient; + + async setup() { + this._gridClient = await GridClient.connect('http://localhost:3333'); + return this._gridClient.playwright(); + } + + async teardown() { + await this._gridClient.close(); + } +} + +export class DefaultTestMode implements TestMode { + async setup() { + return require('playwright-core'); + } + + async teardown() { + } +} diff --git a/tests/config/testModeFixtures.ts b/tests/config/testModeFixtures.ts new file mode 100644 index 0000000000..6f3f2ce91c --- /dev/null +++ b/tests/config/testModeFixtures.ts @@ -0,0 +1,42 @@ +/** + * 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 type { Fixtures } from '@playwright/test'; +import { DefaultTestMode, DriverTestMode, ServiceTestMode, TestModeName } from './testMode'; + +export type TestModeWorkerFixtures = { + mode: TestModeName; + playwright: typeof import('playwright-core'); + toImpl: (rpcObject: any) => any; +}; + +export const testModeFixtures: Fixtures<{}, TestModeWorkerFixtures> = { + mode: [ 'default', { scope: 'worker' } ], + + playwright: [ async ({ mode }, run) => { + const testMode = { + default: new DefaultTestMode(), + service: new ServiceTestMode(), + driver: new DriverTestMode(), + }[mode]; + require('playwright-core/lib/utils/utils').setUnderTest(); + const playwright = await testMode.setup(); + await run(playwright); + await testMode.teardown(); + }, { scope: 'worker' } ], + + toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ], +}; diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts index 5894292e11..1522444131 100644 --- a/tests/electron/electron-app.spec.ts +++ b/tests/electron/electron-app.spec.ts @@ -17,9 +17,8 @@ import type { BrowserWindow } from 'electron'; import path from 'path'; import { electronTest as test, expect } from './electronTest'; -import { baseTest } from '../config/baseTest'; -baseTest('should fire close event', async ({ playwright }) => { +test('should fire close event', async ({ playwright }) => { const electronApp = await playwright._electron.launch({ args: [path.join(__dirname, 'electron-app.js')], }); diff --git a/tests/electron/electronTest.ts b/tests/electron/electronTest.ts index d49e57e2aa..0d755b7a4f 100644 --- a/tests/electron/electronTest.ts +++ b/tests/electron/electronTest.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { baseTest, CommonWorkerFixtures } from '../config/baseTest'; import { ElectronApplication, Page } from 'playwright-core'; -import type { Fixtures } from '@playwright/test'; +import type { Fixtures, PlaywrightWorkerOptions } from '@playwright/test'; import * as path from 'path'; import { PageTestFixtures } from '../page/pageTest'; +import { TestModeWorkerFixtures } from '../config/testModeFixtures'; +import { browserTest } from '../config/browserTest'; export { expect } from '@playwright/test'; type ElectronTestFixtures = PageTestFixtures & { @@ -27,7 +28,7 @@ type ElectronTestFixtures = PageTestFixtures & { }; const electronVersion = require('electron/package.json').version; -export const electronFixtures: Fixtures = { +export const electronFixtures: Fixtures = { browserVersion: electronVersion, browserMajorVersion: Number(electronVersion.split('.')[0]), isAndroid: false, @@ -66,10 +67,9 @@ export const electronFixtures: Fixtures { await run(await newWindow()); }, }; -export const electronTest = baseTest.extend(electronFixtures); +export const electronTest = browserTest.extend(electronFixtures as any); diff --git a/tests/page/page-request-intercept.spec.ts b/tests/page/page-request-intercept.spec.ts index 52442d101b..8ca46dea06 100644 --- a/tests/page/page-request-intercept.spec.ts +++ b/tests/page/page-request-intercept.spec.ts @@ -234,7 +234,7 @@ it('should give access to the intercepted response body', async ({ page, server const routePromise = new Promise(f => routeCallback = f); await page.route('**/simple.json', routeCallback); - const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/simple.json').catch(console.log); + const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/simple.json').catch(() => {}); const route = await routePromise; // @ts-expect-error diff --git a/tests/page/pageTest.ts b/tests/page/pageTest.ts index 6e265ac3de..c06aefd18e 100644 --- a/tests/page/pageTest.ts +++ b/tests/page/pageTest.ts @@ -15,7 +15,8 @@ */ import { baseTest } from '../config/baseTest'; -import type { Page } from 'playwright-core'; +import type { Page, ViewportSize } from 'playwright-core'; +import { VideoMode } from '@playwright/test'; export { expect } from '@playwright/test'; // Page test does not guarantee an isolated context, just a new page (because Android). @@ -27,4 +28,12 @@ export type PageTestFixtures = { isElectron: boolean; }; -export const test = baseTest.declare(); +export type PageWorkerFixtures = { + headless: boolean, + channel: string, + trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace'; + video: VideoMode | { mode: VideoMode, size: ViewportSize }; + browserName: 'chromium' | 'firefox' | 'webkit', +}; + +export const test = baseTest.declare(); diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts index f08ec967e9..8f4fda7e28 100644 --- a/tests/playwright-test/playwright-test-fixtures.ts +++ b/tests/playwright-test/playwright-test-fixtures.ts @@ -20,7 +20,8 @@ import * as os from 'os'; import * as path from 'path'; import rimraf from 'rimraf'; import { promisify } from 'util'; -import { CommonFixtures, commonFixtures, serverFixtures, ServerFixtures } from '../config/commonFixtures'; +import { CommonFixtures, commonFixtures } from '../config/commonFixtures'; +import { serverFixtures, ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures'; import { test as base, TestInfo } from './stable-test-runner'; const removeFolderAsync = promisify(rimraf); @@ -193,7 +194,7 @@ type Fixtures = { }; const common = base.extend(commonFixtures as any); -export const test = common.extend(serverFixtures as any).extend({ +export const test = common.extend(serverFixtures as any).extend({ writeFiles: async ({}, use, testInfo) => { await use(files => writeFiles(testInfo, files)); },