From 029c0788c3d21959d4fc072a2e99aad61525833f Mon Sep 17 00:00:00 2001 From: Florian Schwalm <68847951+egfx-notifications@users.noreply.github.com> Date: Mon, 5 May 2025 16:29:15 +0200 Subject: [PATCH] feat(browser): implement `connect` option for `playwright` browser provider (#7915) Co-authored-by: Vladimir --- docs/guide/browser/playwright.md | 13 ++++- packages/browser/providers/playwright.d.ts | 7 ++- .../browser/src/node/providers/playwright.ts | 20 +++++++ pnpm-lock.yaml | 3 + .../fixtures/playwright-connect/basic.test.js | 12 ++++ .../playwright-connect/vitest.config.js | 17 ++++++ test/browser/package.json | 1 + test/browser/specs/playwright-connect.test.ts | 57 +++++++++++++++++++ 8 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 test/browser/fixtures/playwright-connect/basic.test.js create mode 100644 test/browser/fixtures/playwright-connect/vitest.config.js create mode 100644 test/browser/specs/playwright-connect.test.ts diff --git a/docs/guide/browser/playwright.md b/docs/guide/browser/playwright.md index 985e693bb..2601fcc66 100644 --- a/docs/guide/browser/playwright.md +++ b/docs/guide/browser/playwright.md @@ -16,9 +16,9 @@ Alternatively, you can also add it to `compilerOptions.types` field in your `tsc } ``` -Vitest opens a single page to run all tests in the same file. You can configure the `launch` and `context` properties in `instances`: +Vitest opens a single page to run all tests in the same file. You can configure the `launch`, `connect` and `context` properties in `instances`: -```ts{9-10} [vitest.config.ts] +```ts{9-11} [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ @@ -28,6 +28,7 @@ export default defineConfig({ { browser: 'firefox', launch: {}, + connect: {}, context: {}, }, ], @@ -65,6 +66,14 @@ Vitest will ignore `launch.headless` option. Instead, use [`test.browser.headles Note that Vitest will push debugging flags to `launch.args` if [`--inspect`](/guide/cli#inspect) is enabled. ::: +## connect 3.2.0 {#connect} + +These options are directly passed down to `playwright[browser].connect` command. You can read more about the command and available arguments in the [Playwright documentation](https://playwright.dev/docs/api/class-browsertype#browser-type-connect). + +::: warning +Since this command connects to an existing Playwright server, any `launch` options will be ignored. +::: + ## context Vitest creates a new context for every test file by calling [`browser.newContext()`](https://playwright.dev/docs/api/class-browsercontext). You can configure this behaviour by specifying [custom arguments](https://playwright.dev/docs/api/class-apirequest#api-request-new-context). diff --git a/packages/browser/providers/playwright.d.ts b/packages/browser/providers/playwright.d.ts index c1d2adda0..8b607f9e9 100644 --- a/packages/browser/providers/playwright.d.ts +++ b/packages/browser/providers/playwright.d.ts @@ -5,7 +5,8 @@ import type { FrameLocator, LaunchOptions, Page, - CDPSession + CDPSession, + ConnectOptions } from 'playwright' import { Protocol } from 'playwright-core/types/protocol' import '../matchers.js' @@ -14,6 +15,10 @@ import type {} from "vitest/node" declare module 'vitest/node' { export interface BrowserProviderOptions { launch?: LaunchOptions + connect?: { + wsEndpoint: string + options?: ConnectOptions + } context?: Omit< BrowserContextOptions, 'ignoreHTTPSErrors' | 'serviceWorkers' diff --git a/packages/browser/src/node/providers/playwright.ts b/packages/browser/src/node/providers/playwright.ts index 3045d959d..228504a2f 100644 --- a/packages/browser/src/node/providers/playwright.ts +++ b/packages/browser/src/node/providers/playwright.ts @@ -3,6 +3,7 @@ import type { Browser, BrowserContext, BrowserContextOptions, + ConnectOptions, Frame, FrameLocator, LaunchOptions, @@ -18,6 +19,7 @@ import type { TestProject, } from 'vitest/node' import { createManualModuleSource } from '@vitest/mocker/node' +import c from 'tinyrainbow' import { createDebugger } from 'vitest/node' const debug = createDebugger('vitest:browser:playwright') @@ -41,6 +43,10 @@ export class PlaywrightBrowserProvider implements BrowserProvider { private options?: { launch?: LaunchOptions + connect?: { + wsEndpoint: string + options?: ConnectOptions + } context?: BrowserContextOptions & { actionTimeout?: number } } @@ -86,6 +92,20 @@ export class PlaywrightBrowserProvider implements BrowserProvider { const playwright = await import('playwright') + if (this.options?.connect) { + if (this.options.launch) { + this.project.vitest.logger.warn( + c.yellow(`Found both ${c.bold(c.italic(c.yellow('connect')))} and ${c.bold(c.italic(c.yellow('launch')))} options in browser instance configuration. + Ignoring ${c.bold(c.italic(c.yellow('launch')))} options and using ${c.bold(c.italic(c.yellow('connect')))} mode. + You probably want to remove one of the two options and keep only the one you want to use.`), + ) + } + const browser = await playwright[this.browserName].connect(this.options.connect.wsEndpoint, this.options.connect.options) + this.browser = browser + this.browserPromise = null + return this.browser + } + const launchOptions = { ...this.options?.launch, headless: options.headless, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48e7563d1..e96c49b84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1159,6 +1159,9 @@ importers: '@vitest/injected-lib': specifier: link:./injected-lib version: link:injected-lib + playwright: + specifier: ^1.52.0 + version: 1.52.0 react: specifier: ^19.1.0 version: 19.1.0 diff --git a/test/browser/fixtures/playwright-connect/basic.test.js b/test/browser/fixtures/playwright-connect/basic.test.js new file mode 100644 index 000000000..107d7cee9 --- /dev/null +++ b/test/browser/fixtures/playwright-connect/basic.test.js @@ -0,0 +1,12 @@ +import { test, expect } from 'vitest'; + +test('[playwright] Run basic test in browser via connect mode', () => { + expect(1).toBe(1) +}) + +test('[playwright] Run browser-only test in browser via connect mode', () => { + const element = document.createElement("div") + expect(element instanceof HTMLDivElement).toBe(true) + expect(element instanceof HTMLElement).toBe(true) + expect(element instanceof HTMLInputElement).not.toBe(true) +}) diff --git a/test/browser/fixtures/playwright-connect/vitest.config.js b/test/browser/fixtures/playwright-connect/vitest.config.js new file mode 100644 index 000000000..e86783018 --- /dev/null +++ b/test/browser/fixtures/playwright-connect/vitest.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config'; +import { fileURLToPath } from 'node:url' + +const provider = process.env.PROVIDER || 'playwright' + +export default defineConfig({ + clearScreen: false, + cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)), + test: { + browser: { + provider: provider, + enabled: true, + headless: true, + screenshotFailures: false, + }, + }, +}) diff --git a/test/browser/package.json b/test/browser/package.json index 501d8c847..15c19b488 100644 --- a/test/browser/package.json +++ b/test/browser/package.json @@ -31,6 +31,7 @@ "@vitest/bundled-lib": "link:./bundled-lib", "@vitest/cjs-lib": "link:./cjs-lib", "@vitest/injected-lib": "link:./injected-lib", + "playwright": "^1.52.0", "react": "^19.1.0", "react-dom": "^19.1.0", "url": "^0.11.4", diff --git a/test/browser/specs/playwright-connect.test.ts b/test/browser/specs/playwright-connect.test.ts new file mode 100644 index 000000000..ec5130c2e --- /dev/null +++ b/test/browser/specs/playwright-connect.test.ts @@ -0,0 +1,57 @@ +import { chromium } from 'playwright' +import { expect, test } from 'vitest' +import { provider } from '../settings' +import { runBrowserTests } from './utils' + +test.runIf(provider === 'playwright')('[playwright] runs in connect mode', async () => { + const browserServer = await chromium.launchServer() + const wsEndpoint = browserServer.wsEndpoint() + + const { stdout, exitCode, stderr } = await runBrowserTests({ + root: './fixtures/playwright-connect', + browser: { + instances: [ + { + browser: 'chromium', + name: 'chromium', + connect: { + wsEndpoint, + }, + }, + ], + }, + }) + + await browserServer.close() + + expect(stdout).toContain('Tests 2 passed') + expect(exitCode).toBe(0) + expect(stderr).toBe('') +}) + +test.runIf(provider === 'playwright')('[playwright] warns if both connect and launch mode are configured', async () => { + const browserServer = await chromium.launchServer() + const wsEndpoint = browserServer.wsEndpoint() + + const { stdout, exitCode, stderr } = await runBrowserTests({ + root: './fixtures/playwright-connect', + browser: { + instances: [ + { + browser: 'chromium', + name: 'chromium', + connect: { + wsEndpoint, + }, + launch: {}, + }, + ], + }, + }) + + await browserServer.close() + + expect(stdout).toContain('Tests 2 passed') + expect(exitCode).toBe(0) + expect(stderr).toContain('Found both connect and launch options in browser instance configuration.') +}) -- 2.51.2