Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: move browser providers to @vitest/browser package #4364

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 6 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,23 @@ jobs:
- uses: browser-actions/setup-chrome@v1
- uses: browser-actions/setup-firefox@v1
- uses: browser-actions/setup-edge@v1
id: setup-edge
with:
edge-version: beta
edge-version: stable

- name: Install
run: pnpm i

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

- name: Build
run: pnpm run build

- name: Test Browser (webdriverio)
run: pnpm run test:browser:webdriverio
env:
EDGEDRIVER_VERSION: ${{ steps.setup-edge.outputs.edge-version }}

- name: Test Browser (playwright)
run: pnpm run test:browser:playwright
Expand Down Expand Up @@ -169,7 +172,7 @@ jobs:
run: pnpm i

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

- name: Build
run: pnpm run build
Expand All @@ -181,29 +184,3 @@ jobs:
run: pnpm run test:browser:playwright
env:
BROWSER: ${{ matrix.browser[1] }}

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

steps:
- uses: actions/checkout@v4

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

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

- name: Build
run: sudo pnpm run build

- name: Enable
run: sudo safaridriver --enable

- 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
38 changes: 37 additions & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,7 @@ Path to a provider that will be used when running browser tests. Vitest provides
export interface BrowserProvider {
name: string
getSupportedBrowsers(): readonly string[]
initialize(ctx: Vitest, options: { browser: string }): Awaitable<void>
initialize(ctx: Vitest, options: { browser: string; options?: BrowserProviderOptions }): Awaitable<void>
openPage(url: string): Awaitable<void>
close(): Awaitable<void>
}
Expand All @@ -1342,6 +1342,42 @@ export interface BrowserProvider {
This is an advanced API for library authors. If you just need to run tests in a browser, use the [browser](/config/#browser) option.
:::

#### browser.providerOptions

- **Type:** `BrowserProviderOptions`
- **Version:** Since Vitest 1.0.0-beta.3

Options that will be passed down to provider when calling `provider.initialize`.

```ts
export default defineConfig({
test: {
browser: {
providerOptions: {
launch: {
devtools: true,
}
}
}
}
})
```

::: tip
To have a better type safety when using built-in providers, you can add one of these types (for provider that you are using) to your tsconfig's `compilerOptions.types` field:

```json
{
"compilerOptions": {
"types": [
"@vitest/browser/providers/webdriverio",
"@vitest/browser/providers/playwright"
]
}
}
```
:::

#### browser.slowHijackESM

- **Type:** `boolean`
Expand Down
4 changes: 2 additions & 2 deletions examples/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"test:ui": "vite build && vitest --ui"
},
"devDependencies": {
"@playwright/test": "^1.28.0",
"@playwright/test": "^1.39.0",
"@vitest/ui": "latest",
"playwright": "^1.28.0",
"playwright": "^1.39.0",
"vite": "latest",
"vitest": "latest"
}
Expand Down
35 changes: 32 additions & 3 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./providers": {
"types": "./providers.d.ts",
"import": "./dist/providers.js"
},
"./providers/webdriverio": {
"types": "./dist/providers/webdriverio.d.ts"
},
"./providers/playwright": {
"types": "./dist/providers/playwright.d.ts"
},
"./*": "./*"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"stubs"
"providers"
],
"scripts": {
"build": "rimraf dist && pnpm build:node && pnpm build:client",
Expand All @@ -39,7 +49,21 @@
"prepublishOnly": "pnpm build"
},
"peerDependencies": {
"vitest": "^1.0.0-0"
"playwright": "*",
"safaridriver": "*",
"vitest": "^1.0.0-0",
"webdriverio": "*"
},
"peerDependenciesMeta": {
"webdriverio": {
"optional": true
},
"safaridriver": {
"optional": true
},
"playwright": {
"optional": true
}
},
"dependencies": {
"estree-walker": "^3.0.3",
Expand All @@ -52,7 +76,12 @@
"@vitest/runner": "workspace:*",
"@vitest/ui": "workspace:*",
"@vitest/ws-client": "workspace:*",
"@wdio/protocols": "^8.18.0",
"periscopic": "^3.1.0",
"vitest": "workspace:*"
"playwright": "^1.39.0",
"playwright-core": "^1.39.0",
"safaridriver": "^0.1.0",
"vitest": "workspace:*",
"webdriverio": "^8.20.0"
}
}
6 changes: 6 additions & 0 deletions packages/browser/providers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { BrowserProvider } from 'vitest/nide'

declare var webdriverio: BrowserProvider
declare var playwright: BrowserProvider

export { webdriverio, playwright }
8 changes: 8 additions & 0 deletions packages/browser/providers/playwright.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Browser, LaunchOptions } from 'playwright'

declare module 'vitest/node' {
interface BrowserProviderOptions {
launch?: LaunchOptions
page?: Parameters<Browser['newPage']>[0]
}
}
5 changes: 5 additions & 0 deletions packages/browser/providers/webdriverio.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { RemoteOptions } from 'webdriverio'

declare module 'vitest/node' {
interface BrowserProviderOptions extends RemoteOptions {}
}
13 changes: 9 additions & 4 deletions packages/browser/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import pkg from './package.json' assert { type: 'json' }
const external = [
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies || {}),
'vitest/node',
'vitest',
'worker_threads',
'node:worker_threads',
]
Expand All @@ -23,11 +25,14 @@ const plugins = [
}),
]

const input = {
index: './src/node/index.ts',
providers: './src/node/providers/index.ts',
}

export default () => [
{
input: [
'./src/node/index.ts',
],
input,
output: {
dir: 'dist',
format: 'esm',
Expand All @@ -36,7 +41,7 @@ export default () => [
plugins,
},
{
input: './src/node/index.ts',
input: input.index,
output: {
file: 'dist/index.d.ts',
format: 'esm',
Expand Down
7 changes: 6 additions & 1 deletion packages/browser/src/client/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ async function defaultErrorReport(type: string, unhandledError: any) {
message: unhandledError.message,
stack: unhandledError.stack,
}
if (testId !== 'no-isolate')
error.VITEST_TEST_PATH = testId
await client.rpc.onUnhandledError(error, type)
await client.rpc.onDone(testId)
}
Expand All @@ -81,7 +83,10 @@ let runningTests = false

async function reportUnexpectedError(rpc: typeof client.rpc, type: string, error: any) {
const { processError } = await importId('vitest/browser') as typeof import('vitest/browser')
await rpc.onUnhandledError(processError(error), type)
const processedError = processError(error)
if (testId !== 'no-isolate')
error.VITEST_TEST_PATH = testId
await rpc.onUnhandledError(processedError, type)
if (!runningTests)
await rpc.onDone(testId)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/browser/src/node/providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PlaywrightBrowserProvider } from './playwright'
import { WebdriverBrowserProvider } from './webdriver'

export const webdriverio = WebdriverBrowserProvider
export const playwright = PlaywrightBrowserProvider
80 changes: 80 additions & 0 deletions packages/browser/src/node/providers/playwright.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Browser, LaunchOptions, Page } from 'playwright'
import type { BrowserProvider, BrowserProviderInitializationOptions, WorkspaceProject } from 'vitest/node'
import { ensurePackageInstalled } from 'vitest/node'

type Awaitable<T> = T | PromiseLike<T>

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

export interface PlaywrightProviderOptions extends BrowserProviderInitializationOptions {
browser: PlaywrightBrowser
}

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

private cachedBrowser: Browser | null = null
private cachedPage: Page | null = null
private browser!: PlaywrightBrowser
private ctx!: WorkspaceProject

private options?: {
launch?: LaunchOptions
page?: Parameters<Browser['newPage']>[0]
}

getSupportedBrowsers() {
return playwrightBrowsers
}

async initialize(ctx: WorkspaceProject, { browser, options }: PlaywrightProviderOptions) {
this.ctx = ctx
this.browser = browser
this.options = options as any

const root = this.ctx.config.root

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

private async openBrowserPage() {
if (this.cachedPage)
return this.cachedPage

const options = this.ctx.config.browser

const playwright = await import('playwright')

const browser = await playwright[this.browser].launch({
...this.options?.launch,
headless: options.headless,
})
this.cachedBrowser = browser
this.cachedPage = await browser.newPage(this.options?.page)

this.cachedPage.on('close', () => {
browser.close()
})

return this.cachedPage
}

catchError(cb: (error: Error) => Awaitable<void>) {
this.cachedPage?.on('pageerror', cb)
return () => {
this.cachedPage?.off('pageerror', cb)
}
}

async openPage(url: string) {
const browserPage = await this.openBrowserPage()
await browserPage.goto(url)
}

async close() {
await this.cachedPage?.close()
await this.cachedBrowser?.close()
}
}
Loading
Loading