Skip to content

Commit

Permalink
feat: playwright as browser provider (#3079)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
  • Loading branch information
Aslemammad and sheremet-va committed Mar 27, 2023
1 parent 280ad1e commit 9dc6929
Show file tree
Hide file tree
Showing 22 changed files with 239 additions and 144 deletions.
32 changes: 19 additions & 13 deletions .github/workflows/ci.yml
Expand Up @@ -106,12 +106,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chrome, firefox, edge]
browser: [[chrome, chromium], [firefox, firefox], [edge, webkit]]

timeout-minutes: 10

env:
BROWSER: ${{ matrix.browser }}
BROWSER: ${{ matrix.browser[0] }}
steps:
- uses: actions/checkout@v3

Expand All @@ -126,36 +126,42 @@ jobs:
- name: Install
run: pnpm i

- name: Install Playwright Dependencies
run: pnpx playwright install-deps

- name: Build
run: pnpm run build

- name: Test Browser
run: pnpm run browser:test
- name: Test Browser (webdriverio)
run: pnpm run test:browser:webdriverio

- name: Test Browser (playwright)
run: pnpm run test:browser:playwright
env:
BROWSER: ${{ matrix.browser[1] }}

test-browser-safari:
runs-on: macos-latest
timeout-minutes: 10

env:
BROWSER: safari
steps:
- uses: actions/checkout@v3

- uses: ./.github/actions/setup-and-cache
with:
node-version: 18

- name: Info
run: system_profiler SPSoftwareDataType

- name: Install
run: pnpm i
run: sudo pnpm i

- name: Build
run: pnpm run build
run: sudo pnpm run build

- name: Enable
run: sudo safaridriver --enable

- name: Test Browser
run: sudo pnpm run browser:test
- name: Test Browser (webdriverio)
run: sudo BROWSER=safari pnpm run test:browser:webdriverio

- name: Test Browser (playwright)
run: sudo BROWSER=webkit pnpm run test:browser:playwright
26 changes: 13 additions & 13 deletions docs/config/index.md
Expand Up @@ -1001,7 +1001,7 @@ Listen to port and serve API. When set to true, the default port is 51204
- **Version:** Since Vitest 0.29.4
- **CLI:** `--browser`, `--browser=<name>`, `--browser.name=chrome --browser.headless`

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.
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.

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

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

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

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

#### browser.headless

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

#### browser.provider

- **Type:** `string`
- **Type:** `'webdriverio' | 'playwright' | string`
- **Default:** `'webdriverio'`
- **CLI:** `--browser.provider=./custom-provider.ts`
- **CLI:** `--browser.provider=playwright`

Path to a provider that will be used when running browser tests. Provider should be exported using `default` export and have this shape:
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:

```ts
export interface BrowserProvider {
initialize(ctx: Vitest): Awaitable<void>
createPool(): {
runTests: (files: string[], invalidated: string[]) => void
close: () => Awaited<void>
}
// signals that test file stopped running, if it was opened with `id=` query
testFinished?(testId: string): Awaitable<void>
name: string
getSupportedBrowsers(): readonly string[]
initialize(ctx: Vitest, options: { browser: string }): Awaitable<void>
openPage(url: string): Awaitable<void>
close(): Awaitable<void>
}
```

Expand Down
32 changes: 13 additions & 19 deletions docs/guide/browser.md
Expand Up @@ -14,32 +14,26 @@ To activate browser mode in your Vitest configuration, you can use the `--browse
export default defineConfig({
test: {
browser: {
enabled: true
enabled: true,
name: 'chrome', // browser name is required
},
}
})
```

## Browser Option Types:

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:
- `firefox`
- `chrome`
- `edge`
- `safari`

Here's an example configuration setting chrome as the browser option:
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:

```ts
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chrome',
},
}
})
```
- `webdriverio` (default) supports these browsers:
- `firefox`
- `chrome`
- `edge`
- `safari`
- `playwright` supports these browsers:
- `firefox`
- `webkit`
- `chromium`

## Cross-browser Testing:

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

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

Additionally, when running your tests, Vitest will attempt to install some drivers for compatibility with `safaridriver`.
:::
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -29,7 +29,8 @@
"ui:build": "vite build packages/ui",
"ui:dev": "vite packages/ui",
"ui:test": "npm -C packages/ui run test:run",
"browser:test": "npm -C test/browser run test"
"test:browser:webdriverio": "npm -C test/browser run test:webdriverio",
"test:browser:playwright": "npm -C test/browser run test:playwright"
},
"devDependencies": {
"@antfu/eslint-config": "^0.34.1",
Expand Down
9 changes: 8 additions & 1 deletion packages/vitest/package.json
Expand Up @@ -102,7 +102,10 @@
"@vitest/browser": "*",
"@vitest/ui": "*",
"happy-dom": "*",
"jsdom": "*"
"jsdom": "*",
"playwright": "*",
"safaridriver": "*",
"webdriverio": "*"
},
"peerDependenciesMeta": {
"@vitest/ui": {
Expand All @@ -123,6 +126,9 @@
"safaridriver": {
"optional": true
},
"playwright": {
"optional": true
},
"@edge-runtime/vm": {
"optional": true
}
Expand Down Expand Up @@ -186,6 +192,7 @@
"natural-compare": "^1.4.0",
"p-limit": "^4.0.0",
"pkg-types": "^1.0.1",
"playwright": "^1.28.0",
"pretty-format": "^27.5.1",
"prompts": "^2.4.2",
"rollup": "^2.79.1",
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/rollup.config.js
Expand Up @@ -53,6 +53,7 @@ const external = [
'inspector',
'webdriverio',
'safaridriver',
'playwright',
'vite-node/source-map',
'vite-node/client',
'vite-node/server',
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/api/setup.ts
Expand Up @@ -36,7 +36,7 @@ export function setup(ctx: Vitest, server?: ViteDevServer) {
const rpc = createBirpc<WebSocketEvents, WebSocketHandlers>(
{
async onDone(testId) {
await ctx.browserProvider?.testFinished?.(testId)
return ctx.state.browserTestPromises.get(testId)?.resolve(true)
},
async onCollected(files) {
ctx.state.collectFiles(files)
Expand Down
6 changes: 0 additions & 6 deletions packages/vitest/src/defaults.ts
Expand Up @@ -39,11 +39,6 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'],
}

export const browserConfigDefaults = {
enabled: false,
headless: isCI,
} as const

export const fakeTimersDefaults = {
loopLimit: 10_000,
shouldClearNativeTimers: true,
Expand Down Expand Up @@ -73,7 +68,6 @@ const config = {
hookTimeout: 10000,
teardownTimeout: 10000,
isolate: true,
browser: browserConfigDefaults,
watchExclude: ['**/node_modules/**', '**/dist/**'],
forceRerunTriggers: [
'**/package.json/**',
Expand Down
14 changes: 12 additions & 2 deletions packages/vitest/src/integrations/browser.ts
@@ -1,3 +1,4 @@
import { PlaywrightBrowserProvider } from '../node/browser/playwright'
import { WebdriverBrowserProvider } from '../node/browser/webdriver'
import type { BrowserProviderModule, ResolvedBrowserOptions } from '../types/browser'

Expand All @@ -6,8 +7,17 @@ interface Loader {
}

export async function getBrowserProvider(options: ResolvedBrowserOptions, loader: Loader): Promise<BrowserProviderModule> {
if (!options.provider || options.provider === 'webdriverio')
return WebdriverBrowserProvider
switch (options.provider) {
case undefined:
case 'webdriverio':
return WebdriverBrowserProvider

case 'playwright':
return PlaywrightBrowserProvider

default:
break
}

let customProviderModule

Expand Down
63 changes: 63 additions & 0 deletions packages/vitest/src/node/browser/playwright.ts
@@ -0,0 +1,63 @@
import type { Page } from 'playwright'
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
import { ensurePackageInstalled } from '../pkg'
import type { Vitest } from '../core'

export const playwrightBrowsers = ['firefox', 'webkit', 'chromium'] as const
export type PlaywrightBrowser = typeof playwrightBrowsers[number]

export interface PlaywrightProviderOptions extends BrowserProviderOptions {
browser: PlaywrightBrowser
}

export class PlaywrightBrowserProvider implements BrowserProvider {
public name = 'playwright'

private cachedBrowser: Page | null = null
private browser!: PlaywrightBrowser
private ctx!: Vitest

getSupportedBrowsers() {
return playwrightBrowsers
}

async initialize(ctx: Vitest, { browser }: PlaywrightProviderOptions) {
this.ctx = ctx
this.browser = browser

const root = this.ctx.config.root

if (!await ensurePackageInstalled('playwright', root))
throw new Error('Cannot find "playwright" package. Please install it manually.')
}

async openBrowser() {
if (this.cachedBrowser)
return this.cachedBrowser

const options = this.ctx.config.browser

const playwright = await import('playwright')

const playwrightInstance = await playwright[this.browser].launch({ headless: options.headless })
this.cachedBrowser = await playwrightInstance.newPage()

this.cachedBrowser.on('close', () => {
playwrightInstance.close()
})

return this.cachedBrowser
}

async openPage(url: string) {
const browserInstance = await this.openBrowser()
await browserInstance.goto(url)
}

async close() {
await this.cachedBrowser?.close()
// TODO: right now process can only exit with timeout, if we use browser
// needs investigating
process.exit()
}
}

0 comments on commit 9dc6929

Please sign in to comment.