Skip to content

Commit 9dc6929

Browse files
feat: playwright as browser provider (#3079)
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
1 parent 280ad1e commit 9dc6929

File tree

22 files changed

+239
-144
lines changed

22 files changed

+239
-144
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ jobs:
106106
runs-on: ubuntu-latest
107107
strategy:
108108
matrix:
109-
browser: [chrome, firefox, edge]
109+
browser: [[chrome, chromium], [firefox, firefox], [edge, webkit]]
110110

111111
timeout-minutes: 10
112112

113113
env:
114-
BROWSER: ${{ matrix.browser }}
114+
BROWSER: ${{ matrix.browser[0] }}
115115
steps:
116116
- uses: actions/checkout@v3
117117

@@ -126,36 +126,42 @@ jobs:
126126
- name: Install
127127
run: pnpm i
128128

129+
- name: Install Playwright Dependencies
130+
run: pnpx playwright install-deps
131+
129132
- name: Build
130133
run: pnpm run build
131134

132-
- name: Test Browser
133-
run: pnpm run browser:test
135+
- name: Test Browser (webdriverio)
136+
run: pnpm run test:browser:webdriverio
137+
138+
- name: Test Browser (playwright)
139+
run: pnpm run test:browser:playwright
140+
env:
141+
BROWSER: ${{ matrix.browser[1] }}
134142

135143
test-browser-safari:
136144
runs-on: macos-latest
137145
timeout-minutes: 10
138146

139-
env:
140-
BROWSER: safari
141147
steps:
142148
- uses: actions/checkout@v3
143149

144150
- uses: ./.github/actions/setup-and-cache
145151
with:
146152
node-version: 18
147153

148-
- name: Info
149-
run: system_profiler SPSoftwareDataType
150-
151154
- name: Install
152-
run: pnpm i
155+
run: sudo pnpm i
153156

154157
- name: Build
155-
run: pnpm run build
158+
run: sudo pnpm run build
156159

157160
- name: Enable
158161
run: sudo safaridriver --enable
159162

160-
- name: Test Browser
161-
run: sudo pnpm run browser:test
163+
- name: Test Browser (webdriverio)
164+
run: sudo BROWSER=safari pnpm run test:browser:webdriverio
165+
166+
- name: Test Browser (playwright)
167+
run: sudo BROWSER=webkit pnpm run test:browser:playwright

docs/config/index.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ Listen to port and serve API. When set to true, the default port is 51204
10011001
- **Version:** Since Vitest 0.29.4
10021002
- **CLI:** `--browser`, `--browser=<name>`, `--browser.name=chrome --browser.headless`
10031003

1004-
Run Vitest tests in a browser. If the browser name is not specified, Vitest will try to determine your default browser automatically. We use [WebdriverIO](https://webdriver.io/) for running tests by default, but it can be configured with [browser.provider](/config/#browser-provider) option.
1004+
Run Vitest tests in a browser. We use [WebdriverIO](https://webdriver.io/) for running tests by default, but it can be configured with [browser.provider](/config/#browser-provider) option.
10051005

10061006
::: tip NOTE
10071007
Read more about testing in a real browser in the [guide page](/guide/browser).
@@ -1022,11 +1022,13 @@ Run all tests inside a browser by default. Can be overriden with [`poolMatchGlob
10221022
#### browser&#46;name
10231023

10241024
- **Type:** `string`
1025-
- **Default:** _tries to find default browser automatically_
10261025
- **CLI:** `--browser=safari`
10271026

1028-
Run all tests in a specific browser. If not specified, tries to find a browser automatically.
1027+
Run all tests in a specific browser. Possible options in different providers:
10291028

1029+
- `webdriverio`: `firefox`, `chrome`, `edge`, `safari`
1030+
- `playwright`: `firefox`, `webkit`, `chromium`
1031+
- custom: any string that will be passed to the provider
10301032

10311033
#### browser.headless
10321034

@@ -1046,21 +1048,19 @@ Configure options for Vite server that serves code in the browser. Does not affe
10461048

10471049
#### browser.provider
10481050

1049-
- **Type:** `string`
1051+
- **Type:** `'webdriverio' | 'playwright' | string`
10501052
- **Default:** `'webdriverio'`
1051-
- **CLI:** `--browser.provider=./custom-provider.ts`
1053+
- **CLI:** `--browser.provider=playwright`
10521054

1053-
Path to a provider that will be used when running browser tests. Provider should be exported using `default` export and have this shape:
1055+
Path to a provider that will be used when running browser tests. Vitest provides two providers which are `webdriverio` (default) and `playwright`. Custom providers should be exported using `default` export and have this shape:
10541056

10551057
```ts
10561058
export interface BrowserProvider {
1057-
initialize(ctx: Vitest): Awaitable<void>
1058-
createPool(): {
1059-
runTests: (files: string[], invalidated: string[]) => void
1060-
close: () => Awaited<void>
1061-
}
1062-
// signals that test file stopped running, if it was opened with `id=` query
1063-
testFinished?(testId: string): Awaitable<void>
1059+
name: string
1060+
getSupportedBrowsers(): readonly string[]
1061+
initialize(ctx: Vitest, options: { browser: string }): Awaitable<void>
1062+
openPage(url: string): Awaitable<void>
1063+
close(): Awaitable<void>
10641064
}
10651065
```
10661066

docs/guide/browser.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,26 @@ To activate browser mode in your Vitest configuration, you can use the `--browse
1414
export default defineConfig({
1515
test: {
1616
browser: {
17-
enabled: true
17+
enabled: true,
18+
name: 'chrome', // browser name is required
1819
},
1920
}
2021
})
2122
```
2223

2324
## Browser Option Types:
2425

25-
The browser option in Vitest can be set to either a boolean or a string type. If set to `true`, Vitest will try to automatically find your default browser. You can also specify a browser by providing its name as a `string`. The available browser options are:
26-
- `firefox`
27-
- `chrome`
28-
- `edge`
29-
- `safari`
30-
31-
Here's an example configuration setting chrome as the browser option:
26+
The browser option in Vitest depends on the provider. Vitest will fail, if you pass `--browser` and don't specify its name in the config file. Available options:
3227

33-
```ts
34-
export default defineConfig({
35-
test: {
36-
browser: {
37-
enabled: true,
38-
name: 'chrome',
39-
},
40-
}
41-
})
42-
```
28+
- `webdriverio` (default) supports these browsers:
29+
- `firefox`
30+
- `chrome`
31+
- `edge`
32+
- `safari`
33+
- `playwright` supports these browsers:
34+
- `firefox`
35+
- `webkit`
36+
- `chromium`
4337

4438
## Cross-browser Testing:
4539

@@ -58,7 +52,7 @@ npx vitest --browser.name=chrome --browser.headless
5852
```
5953

6054
::: tip NOTE
61-
When using the Safari browser option, the `safaridriver` needs to be activated by running `sudo safaridriver --enable` on your device.
55+
When using the Safari browser option with WebdriverIO, the `safaridriver` needs to be activated by running `sudo safaridriver --enable` on your device.
6256

6357
Additionally, when running your tests, Vitest will attempt to install some drivers for compatibility with `safaridriver`.
6458
:::

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"ui:build": "vite build packages/ui",
3030
"ui:dev": "vite packages/ui",
3131
"ui:test": "npm -C packages/ui run test:run",
32-
"browser:test": "npm -C test/browser run test"
32+
"test:browser:webdriverio": "npm -C test/browser run test:webdriverio",
33+
"test:browser:playwright": "npm -C test/browser run test:playwright"
3334
},
3435
"devDependencies": {
3536
"@antfu/eslint-config": "^0.34.1",

packages/vitest/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@
102102
"@vitest/browser": "*",
103103
"@vitest/ui": "*",
104104
"happy-dom": "*",
105-
"jsdom": "*"
105+
"jsdom": "*",
106+
"playwright": "*",
107+
"safaridriver": "*",
108+
"webdriverio": "*"
106109
},
107110
"peerDependenciesMeta": {
108111
"@vitest/ui": {
@@ -123,6 +126,9 @@
123126
"safaridriver": {
124127
"optional": true
125128
},
129+
"playwright": {
130+
"optional": true
131+
},
126132
"@edge-runtime/vm": {
127133
"optional": true
128134
}
@@ -186,6 +192,7 @@
186192
"natural-compare": "^1.4.0",
187193
"p-limit": "^4.0.0",
188194
"pkg-types": "^1.0.1",
195+
"playwright": "^1.28.0",
189196
"pretty-format": "^27.5.1",
190197
"prompts": "^2.4.2",
191198
"rollup": "^2.79.1",

packages/vitest/rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const external = [
5353
'inspector',
5454
'webdriverio',
5555
'safaridriver',
56+
'playwright',
5657
'vite-node/source-map',
5758
'vite-node/client',
5859
'vite-node/server',

packages/vitest/src/api/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function setup(ctx: Vitest, server?: ViteDevServer) {
3636
const rpc = createBirpc<WebSocketEvents, WebSocketHandlers>(
3737
{
3838
async onDone(testId) {
39-
await ctx.browserProvider?.testFinished?.(testId)
39+
return ctx.state.browserTestPromises.get(testId)?.resolve(true)
4040
},
4141
async onCollected(files) {
4242
ctx.state.collectFiles(files)

packages/vitest/src/defaults.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
3939
extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'],
4040
}
4141

42-
export const browserConfigDefaults = {
43-
enabled: false,
44-
headless: isCI,
45-
} as const
46-
4742
export const fakeTimersDefaults = {
4843
loopLimit: 10_000,
4944
shouldClearNativeTimers: true,
@@ -73,7 +68,6 @@ const config = {
7368
hookTimeout: 10000,
7469
teardownTimeout: 10000,
7570
isolate: true,
76-
browser: browserConfigDefaults,
7771
watchExclude: ['**/node_modules/**', '**/dist/**'],
7872
forceRerunTriggers: [
7973
'**/package.json/**',

packages/vitest/src/integrations/browser.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PlaywrightBrowserProvider } from '../node/browser/playwright'
12
import { WebdriverBrowserProvider } from '../node/browser/webdriver'
23
import type { BrowserProviderModule, ResolvedBrowserOptions } from '../types/browser'
34

@@ -6,8 +7,17 @@ interface Loader {
67
}
78

89
export async function getBrowserProvider(options: ResolvedBrowserOptions, loader: Loader): Promise<BrowserProviderModule> {
9-
if (!options.provider || options.provider === 'webdriverio')
10-
return WebdriverBrowserProvider
10+
switch (options.provider) {
11+
case undefined:
12+
case 'webdriverio':
13+
return WebdriverBrowserProvider
14+
15+
case 'playwright':
16+
return PlaywrightBrowserProvider
17+
18+
default:
19+
break
20+
}
1121

1222
let customProviderModule
1323

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { Page } from 'playwright'
2+
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
3+
import { ensurePackageInstalled } from '../pkg'
4+
import type { Vitest } from '../core'
5+
6+
export const playwrightBrowsers = ['firefox', 'webkit', 'chromium'] as const
7+
export type PlaywrightBrowser = typeof playwrightBrowsers[number]
8+
9+
export interface PlaywrightProviderOptions extends BrowserProviderOptions {
10+
browser: PlaywrightBrowser
11+
}
12+
13+
export class PlaywrightBrowserProvider implements BrowserProvider {
14+
public name = 'playwright'
15+
16+
private cachedBrowser: Page | null = null
17+
private browser!: PlaywrightBrowser
18+
private ctx!: Vitest
19+
20+
getSupportedBrowsers() {
21+
return playwrightBrowsers
22+
}
23+
24+
async initialize(ctx: Vitest, { browser }: PlaywrightProviderOptions) {
25+
this.ctx = ctx
26+
this.browser = browser
27+
28+
const root = this.ctx.config.root
29+
30+
if (!await ensurePackageInstalled('playwright', root))
31+
throw new Error('Cannot find "playwright" package. Please install it manually.')
32+
}
33+
34+
async openBrowser() {
35+
if (this.cachedBrowser)
36+
return this.cachedBrowser
37+
38+
const options = this.ctx.config.browser
39+
40+
const playwright = await import('playwright')
41+
42+
const playwrightInstance = await playwright[this.browser].launch({ headless: options.headless })
43+
this.cachedBrowser = await playwrightInstance.newPage()
44+
45+
this.cachedBrowser.on('close', () => {
46+
playwrightInstance.close()
47+
})
48+
49+
return this.cachedBrowser
50+
}
51+
52+
async openPage(url: string) {
53+
const browserInstance = await this.openBrowser()
54+
await browserInstance.goto(url)
55+
}
56+
57+
async close() {
58+
await this.cachedBrowser?.close()
59+
// TODO: right now process can only exit with timeout, if we use browser
60+
// needs investigating
61+
process.exit()
62+
}
63+
}

0 commit comments

Comments
 (0)