mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-02-01 17:36:51 +00:00
feat!: move browser providers to @vitest/browser package (#4364)
This commit is contained in:
parent
2af2ba7a16
commit
5cdeb558c2
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
}
|
||||
@ -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`
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -20,6 +20,16 @@
|
||||
"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",
|
||||
@ -27,7 +37,7 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"stubs"
|
||||
"providers"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && pnpm build:node && pnpm build:client",
|
||||
@ -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",
|
||||
@ -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
packages/browser/providers.d.ts
vendored
Normal file
6
packages/browser/providers.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
import type { BrowserProvider } from 'vitest/nide'
|
||||
|
||||
declare var webdriverio: BrowserProvider
|
||||
declare var playwright: BrowserProvider
|
||||
|
||||
export { webdriverio, playwright }
|
||||
8
packages/browser/providers/playwright.d.ts
vendored
Normal file
8
packages/browser/providers/playwright.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type { Browser, LaunchOptions } from 'playwright'
|
||||
|
||||
declare module 'vitest/node' {
|
||||
interface BrowserProviderOptions {
|
||||
launch?: LaunchOptions
|
||||
page?: Parameters<Browser['newPage']>[0]
|
||||
}
|
||||
}
|
||||
5
packages/browser/providers/webdriverio.d.ts
vendored
Normal file
5
packages/browser/providers/webdriverio.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import type { RemoteOptions } from 'webdriverio'
|
||||
|
||||
declare module 'vitest/node' {
|
||||
interface BrowserProviderOptions extends RemoteOptions {}
|
||||
}
|
||||
@ -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',
|
||||
]
|
||||
@ -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',
|
||||
@ -36,7 +41,7 @@ export default () => [
|
||||
plugins,
|
||||
},
|
||||
{
|
||||
input: './src/node/index.ts',
|
||||
input: input.index,
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'esm',
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
5
packages/browser/src/node/providers/index.ts
Normal file
5
packages/browser/src/node/providers/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { PlaywrightBrowserProvider } from './playwright'
|
||||
import { WebdriverBrowserProvider } from './webdriver'
|
||||
|
||||
export const webdriverio = WebdriverBrowserProvider
|
||||
export const playwright = PlaywrightBrowserProvider
|
||||
80
packages/browser/src/node/providers/playwright.ts
Normal file
80
packages/browser/src/node/providers/playwright.ts
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,34 @@
|
||||
import type { Awaitable } from '@vitest/utils'
|
||||
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
|
||||
import { ensurePackageInstalled } from '../pkg'
|
||||
import type { WorkspaceProject } from '../workspace'
|
||||
import type { BrowserProvider, BrowserProviderInitializationOptions, WorkspaceProject } from 'vitest/node'
|
||||
import { ensurePackageInstalled } from 'vitest/node'
|
||||
import type { Browser, RemoteOptions } from 'webdriverio'
|
||||
|
||||
export const webdriverBrowsers = ['firefox', 'chrome', 'edge', 'safari'] as const
|
||||
export type WebdriverBrowser = typeof webdriverBrowsers[number]
|
||||
type Awaitable<T> = T | PromiseLike<T>
|
||||
|
||||
export interface WebdriverProviderOptions extends BrowserProviderOptions {
|
||||
const webdriverBrowsers = ['firefox', 'chrome', 'edge', 'safari'] as const
|
||||
type WebdriverBrowser = typeof webdriverBrowsers[number]
|
||||
|
||||
interface WebdriverProviderOptions extends BrowserProviderInitializationOptions {
|
||||
browser: WebdriverBrowser
|
||||
}
|
||||
|
||||
export class WebdriverBrowserProvider implements BrowserProvider {
|
||||
public name = 'webdriverio'
|
||||
|
||||
private cachedBrowser: WebdriverIO.Browser | null = null
|
||||
private cachedBrowser: Browser | null = null
|
||||
private stopSafari: () => void = () => {}
|
||||
private browser!: WebdriverBrowser
|
||||
private ctx!: WorkspaceProject
|
||||
|
||||
private options?: RemoteOptions
|
||||
|
||||
getSupportedBrowsers() {
|
||||
return webdriverBrowsers
|
||||
}
|
||||
|
||||
async initialize(ctx: WorkspaceProject, { browser }: WebdriverProviderOptions) {
|
||||
async initialize(ctx: WorkspaceProject, { browser, options }: WebdriverProviderOptions) {
|
||||
this.ctx = ctx
|
||||
this.browser = browser
|
||||
this.options = options as RemoteOptions
|
||||
|
||||
const root = this.ctx.config.root
|
||||
|
||||
@ -42,6 +46,9 @@ export class WebdriverBrowserProvider implements BrowserProvider {
|
||||
const options = this.ctx.config.browser
|
||||
|
||||
if (this.browser === 'safari') {
|
||||
if (options.headless)
|
||||
throw new Error('You\'ve enabled headless mode for Safari but it doesn\'t currently support it.')
|
||||
|
||||
const safaridriver = await import('safaridriver')
|
||||
safaridriver.start({ diagnose: true })
|
||||
this.stopSafari = () => safaridriver.stop()
|
||||
@ -55,16 +62,38 @@ export class WebdriverBrowserProvider implements BrowserProvider {
|
||||
|
||||
// TODO: close everything, if browser is closed from the outside
|
||||
this.cachedBrowser = await remote({
|
||||
...this.options,
|
||||
logLevel: 'error',
|
||||
capabilities: {
|
||||
'browserName': this.browser,
|
||||
'wdio:devtoolsOptions': { headless: options.headless },
|
||||
},
|
||||
capabilities: this.buildCapabilities(),
|
||||
})
|
||||
|
||||
return this.cachedBrowser
|
||||
}
|
||||
|
||||
private buildCapabilities() {
|
||||
const capabilities: RemoteOptions['capabilities'] = {
|
||||
...this.options?.capabilities,
|
||||
browserName: this.browser,
|
||||
}
|
||||
|
||||
const headlessMap = {
|
||||
chrome: ['goog:chromeOptions', ['headless', 'disable-gpu']],
|
||||
firefox: ['moz:firefoxOptions', ['-headless']],
|
||||
edge: ['ms:edgeOptions', ['--headless']],
|
||||
} as const
|
||||
|
||||
const options = this.ctx.config.browser
|
||||
const browser = this.browser
|
||||
if (browser !== 'safari' && options.headless) {
|
||||
const [key, args] = headlessMap[browser]
|
||||
const currentValues = (this.options?.capabilities as any)?.[key] || {}
|
||||
const newArgs = [...currentValues.args || [], ...args]
|
||||
capabilities[key] = { ...currentValues, args: newArgs as any }
|
||||
}
|
||||
|
||||
return capabilities
|
||||
}
|
||||
|
||||
async openPage(url: string) {
|
||||
const browserInstance = await this.openBrowser()
|
||||
await browserInstance.url(url)
|
||||
@ -111,10 +111,7 @@
|
||||
"@vitest/browser": "*",
|
||||
"@vitest/ui": "*",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"playwright": "*",
|
||||
"safaridriver": "*",
|
||||
"webdriverio": "*"
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
@ -132,15 +129,6 @@
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
},
|
||||
"webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"safaridriver": {
|
||||
"optional": true
|
||||
},
|
||||
"playwright": {
|
||||
"optional": true
|
||||
},
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
}
|
||||
@ -198,12 +186,9 @@
|
||||
"mlly": "^1.4.0",
|
||||
"p-limit": "^4.0.0",
|
||||
"pkg-types": "^1.0.3",
|
||||
"playwright": "^1.35.1",
|
||||
"pretty-format": "^29.5.0",
|
||||
"prompts": "^2.4.2",
|
||||
"safaridriver": "^0.0.5",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"webdriverio": "^8.11.2",
|
||||
"ws": "^8.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,9 +59,6 @@ const external = [
|
||||
'rollup',
|
||||
'node:vm',
|
||||
'inspector',
|
||||
'webdriverio',
|
||||
'safaridriver',
|
||||
'playwright',
|
||||
'vite-node/source-map',
|
||||
'vite-node/client',
|
||||
'vite-node/server',
|
||||
@ -92,6 +89,7 @@ const plugins = [
|
||||
export default ({ watch }) => defineConfig([
|
||||
{
|
||||
input: entries,
|
||||
treeshake: true,
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
|
||||
@ -1,28 +1,26 @@
|
||||
import { PlaywrightBrowserProvider } from '../node/browser/playwright'
|
||||
import { WebdriverBrowserProvider } from '../node/browser/webdriver'
|
||||
import { ensurePackageInstalled } from '../node/pkg'
|
||||
import type { BrowserProviderModule, ResolvedBrowserOptions } from '../types/browser'
|
||||
|
||||
interface Loader {
|
||||
executeId: (id: string) => Promise<{ default: BrowserProviderModule }>
|
||||
root: string
|
||||
executeId: (id: string) => any
|
||||
}
|
||||
|
||||
export async function getBrowserProvider(options: ResolvedBrowserOptions, loader: Loader): Promise<BrowserProviderModule> {
|
||||
switch (options.provider) {
|
||||
case undefined:
|
||||
case 'webdriverio':
|
||||
return WebdriverBrowserProvider
|
||||
|
||||
case 'playwright':
|
||||
return PlaywrightBrowserProvider
|
||||
|
||||
default:
|
||||
break
|
||||
if (options.provider == null || options.provider === 'webdriverio' || options.provider === 'playwright') {
|
||||
await ensurePackageInstalled('@vitest/browser', loader.root)
|
||||
const providers = await loader.executeId('@vitest/browser/providers') as {
|
||||
webdriverio: BrowserProviderModule
|
||||
playwright: BrowserProviderModule
|
||||
}
|
||||
const provider = (options.provider || 'webdriverio') as 'webdriverio' | 'playwright'
|
||||
return providers[provider]
|
||||
}
|
||||
|
||||
let customProviderModule
|
||||
|
||||
try {
|
||||
customProviderModule = await loader.executeId(options.provider)
|
||||
customProviderModule = await loader.executeId(options.provider) as { default: BrowserProviderModule }
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to load custom BrowserProvider from ${options.provider}`, { cause: error })
|
||||
|
||||
47
packages/vitest/src/integrations/env/index.ts
vendored
47
packages/vitest/src/integrations/env/index.ts
vendored
@ -1,9 +1,4 @@
|
||||
import { normalize, resolve } from 'pathe'
|
||||
import { resolvePath } from 'mlly'
|
||||
import { ViteNodeRunner } from 'vite-node/client'
|
||||
import type { ViteNodeRunnerOptions } from 'vite-node'
|
||||
import type { BuiltinEnvironment, VitestEnvironment } from '../../types/config'
|
||||
import type { Environment } from '../../types'
|
||||
import type { VitestEnvironment } from '../../types/config'
|
||||
import node from './node'
|
||||
import jsdom from './jsdom'
|
||||
import happy from './happy-dom'
|
||||
@ -24,10 +19,6 @@ export const envPackageNames: Record<Exclude<keyof typeof environments, 'node'>,
|
||||
'edge-runtime': '@edge-runtime/vm',
|
||||
}
|
||||
|
||||
function isBuiltinEnvironment(env: VitestEnvironment): env is BuiltinEnvironment {
|
||||
return env in environments
|
||||
}
|
||||
|
||||
export function getEnvPackageName(env: VitestEnvironment) {
|
||||
if (env === 'node')
|
||||
return null
|
||||
@ -37,39 +28,3 @@ export function getEnvPackageName(env: VitestEnvironment) {
|
||||
return null
|
||||
return `vitest-environment-${env}`
|
||||
}
|
||||
|
||||
const _loaders = new Map<string, ViteNodeRunner>()
|
||||
|
||||
export async function createEnvironmentLoader(options: ViteNodeRunnerOptions) {
|
||||
if (!_loaders.has(options.root)) {
|
||||
const loader = new ViteNodeRunner(options)
|
||||
await loader.executeId('/@vite/env')
|
||||
_loaders.set(options.root, loader)
|
||||
}
|
||||
return _loaders.get(options.root)!
|
||||
}
|
||||
|
||||
export async function loadEnvironment(name: VitestEnvironment, options: ViteNodeRunnerOptions): Promise<Environment> {
|
||||
if (isBuiltinEnvironment(name))
|
||||
return environments[name]
|
||||
const loader = await createEnvironmentLoader(options)
|
||||
const root = loader.root
|
||||
const packageId = name[0] === '.' || name[0] === '/'
|
||||
? resolve(root, name)
|
||||
: await resolvePath(`vitest-environment-${name}`, { url: [root] }) ?? resolve(root, name)
|
||||
const pkg = await loader.executeId(normalize(packageId))
|
||||
if (!pkg || !pkg.default || typeof pkg.default !== 'object') {
|
||||
throw new TypeError(
|
||||
`Environment "${name}" is not a valid environment. `
|
||||
+ `Path "${packageId}" should export default object with a "setup" or/and "setupVM" method.`,
|
||||
)
|
||||
}
|
||||
const environment = pkg.default
|
||||
if (environment.transformMode !== 'web' && environment.transformMode !== 'ssr') {
|
||||
throw new TypeError(
|
||||
`Environment "${name}" is not a valid environment. `
|
||||
+ `Path "${packageId}" should export default object with a "transformMode" method equal to "ssr" or "web".`,
|
||||
)
|
||||
}
|
||||
return environment
|
||||
}
|
||||
|
||||
47
packages/vitest/src/integrations/env/loader.ts
vendored
Normal file
47
packages/vitest/src/integrations/env/loader.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
import { normalize, resolve } from 'pathe'
|
||||
import { resolvePath } from 'mlly'
|
||||
import { ViteNodeRunner } from 'vite-node/client'
|
||||
import type { ViteNodeRunnerOptions } from 'vite-node'
|
||||
import type { BuiltinEnvironment, VitestEnvironment } from '../../types/config'
|
||||
import type { Environment } from '../../types'
|
||||
import { environments } from './index'
|
||||
|
||||
function isBuiltinEnvironment(env: VitestEnvironment): env is BuiltinEnvironment {
|
||||
return env in environments
|
||||
}
|
||||
|
||||
const _loaders = new Map<string, ViteNodeRunner>()
|
||||
|
||||
export async function createEnvironmentLoader(options: ViteNodeRunnerOptions) {
|
||||
if (!_loaders.has(options.root)) {
|
||||
const loader = new ViteNodeRunner(options)
|
||||
await loader.executeId('/@vite/env')
|
||||
_loaders.set(options.root, loader)
|
||||
}
|
||||
return _loaders.get(options.root)!
|
||||
}
|
||||
|
||||
export async function loadEnvironment(name: VitestEnvironment, options: ViteNodeRunnerOptions): Promise<Environment> {
|
||||
if (isBuiltinEnvironment(name))
|
||||
return environments[name]
|
||||
const loader = await createEnvironmentLoader(options)
|
||||
const root = loader.root
|
||||
const packageId = name[0] === '.' || name[0] === '/'
|
||||
? resolve(root, name)
|
||||
: await resolvePath(`vitest-environment-${name}`, { url: [root] }) ?? resolve(root, name)
|
||||
const pkg = await loader.executeId(normalize(packageId))
|
||||
if (!pkg || !pkg.default || typeof pkg.default !== 'object') {
|
||||
throw new TypeError(
|
||||
`Environment "${name}" is not a valid environment. `
|
||||
+ `Path "${packageId}" should export default object with a "setup" or/and "setupVM" method.`,
|
||||
)
|
||||
}
|
||||
const environment = pkg.default
|
||||
if (environment.transformMode !== 'web' && environment.transformMode !== 'ssr') {
|
||||
throw new TypeError(
|
||||
`Environment "${name}" is not a valid environment. `
|
||||
+ `Path "${packageId}" should export default object with a "transformMode" method equal to "ssr" or "web".`,
|
||||
)
|
||||
}
|
||||
return environment
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import type { Page } from 'playwright'
|
||||
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
|
||||
import { ensurePackageInstalled } from '../pkg'
|
||||
import type { WorkspaceProject } from '../workspace'
|
||||
import type { Awaitable } from '../../types'
|
||||
|
||||
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!: WorkspaceProject
|
||||
|
||||
getSupportedBrowsers() {
|
||||
return playwrightBrowsers
|
||||
}
|
||||
|
||||
async initialize(ctx: WorkspaceProject, { 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
|
||||
}
|
||||
|
||||
catchError(cb: (error: Error) => Awaitable<void>) {
|
||||
this.cachedBrowser?.on('pageerror', cb)
|
||||
return () => {
|
||||
this.cachedBrowser?.off('pageerror', cb)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
export type { Vitest } from './core'
|
||||
export type { WorkspaceProject as VitestWorkspace } from './workspace'
|
||||
export type { WorkspaceProject } from './workspace'
|
||||
export { createVitest } from './create'
|
||||
export { VitestPlugin } from './plugins'
|
||||
export { startVitest } from './cli-api'
|
||||
@ -8,3 +8,6 @@ export type { WorkspaceSpec } from './pool'
|
||||
|
||||
export type { TestSequencer, TestSequencerConstructor } from './sequencers/types'
|
||||
export { BaseSequencer } from './sequencers/BaseSequencer'
|
||||
|
||||
export { ensurePackageInstalled } from './pkg'
|
||||
export type { BrowserProviderInitializationOptions, BrowserProvider, BrowserProviderOptions } from '../types/browser'
|
||||
|
||||
@ -6,7 +6,7 @@ export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
|
||||
const ctx = project.ctx
|
||||
return {
|
||||
async onWorkerExit(error, code) {
|
||||
await ctx.logger.printError(error, { type: 'Unexpected Exit' })
|
||||
await ctx.logger.printError(error, { type: 'Unexpected Exit', fullStack: true })
|
||||
process.exit(code || 1)
|
||||
},
|
||||
snapshotSaved(snapshot) {
|
||||
|
||||
@ -356,6 +356,7 @@ export class WorkspaceProject {
|
||||
throw new Error(`[${this.getName()}] Browser name is required. Please, set \`test.browser.name\` option manually.`)
|
||||
if (!supportedBrowsers.includes(browser))
|
||||
throw new Error(`[${this.getName()}] Browser "${browser}" is not supported by the browser provider "${this.browserProvider.name}". Supported browsers: ${supportedBrowsers.join(', ')}.`)
|
||||
await this.browserProvider.initialize(this, { browser })
|
||||
const providerOptions = this.config.browser.providerOptions
|
||||
await this.browserProvider.initialize(this, { browser, options: providerOptions })
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import type { CancelReason } from '@vitest/runner'
|
||||
import type { ResolvedConfig, WorkerGlobalState } from '../types'
|
||||
import type { RunnerRPC, RuntimeRPC } from '../types/rpc'
|
||||
import type { ChildContext } from '../types/child'
|
||||
import { loadEnvironment } from '../integrations/env'
|
||||
import { loadEnvironment } from '../integrations/env/loader'
|
||||
import { mockMap, moduleCache, startViteNode } from './execute'
|
||||
import { createSafeRpc, rpcDone } from './rpc'
|
||||
import { setupInspect } from './inspector'
|
||||
|
||||
@ -9,7 +9,7 @@ import { installSourcemapsSupport } from 'vite-node/source-map'
|
||||
import type { CancelReason } from '@vitest/runner'
|
||||
import type { RunnerRPC, RuntimeRPC, WorkerContext, WorkerGlobalState } from '../types'
|
||||
import { distDir } from '../paths'
|
||||
import { loadEnvironment } from '../integrations/env'
|
||||
import { loadEnvironment } from '../integrations/env/loader'
|
||||
import { startVitestExecutor } from './execute'
|
||||
import { createCustomConsole } from './console'
|
||||
import { createSafeRpc } from './rpc'
|
||||
|
||||
@ -4,7 +4,7 @@ import { workerId as poolId } from 'tinypool'
|
||||
import type { CancelReason } from '@vitest/runner'
|
||||
import type { RunnerRPC, RuntimeRPC, WorkerContext, WorkerGlobalState } from '../types'
|
||||
import { getWorkerState } from '../utils/global'
|
||||
import { loadEnvironment } from '../integrations/env'
|
||||
import { loadEnvironment } from '../integrations/env/loader'
|
||||
import { mockMap, moduleCache, startViteNode } from './execute'
|
||||
import { setupInspect } from './inspector'
|
||||
import { createSafeRpc, rpcDone } from './rpc'
|
||||
|
||||
@ -2,14 +2,15 @@ import type { Awaitable } from '@vitest/utils'
|
||||
import type { WorkspaceProject } from '../node/workspace'
|
||||
import type { ApiConfig } from './config'
|
||||
|
||||
export interface BrowserProviderOptions {
|
||||
export interface BrowserProviderInitializationOptions {
|
||||
browser: string
|
||||
options?: BrowserProviderOptions
|
||||
}
|
||||
|
||||
export interface BrowserProvider {
|
||||
name: string
|
||||
getSupportedBrowsers(): readonly string[]
|
||||
initialize(ctx: WorkspaceProject, options: BrowserProviderOptions): Awaitable<void>
|
||||
initialize(ctx: WorkspaceProject, options: BrowserProviderInitializationOptions): Awaitable<void>
|
||||
openPage(url: string): Awaitable<void>
|
||||
catchError(cb: (error: Error) => Awaitable<void>): () => Awaitable<void>
|
||||
close(): Awaitable<void>
|
||||
@ -19,6 +20,8 @@ export interface BrowserProviderModule {
|
||||
new (): BrowserProvider
|
||||
}
|
||||
|
||||
export interface BrowserProviderOptions {}
|
||||
|
||||
export interface BrowserConfigOptions {
|
||||
/**
|
||||
* if running tests in the browser should be the default
|
||||
@ -33,12 +36,24 @@ export interface BrowserConfigOptions {
|
||||
name: string
|
||||
|
||||
/**
|
||||
* browser provider
|
||||
* Browser provider
|
||||
*
|
||||
* @default 'webdriverio'
|
||||
*/
|
||||
provider?: 'webdriverio' | 'playwright' | (string & {})
|
||||
|
||||
/**
|
||||
* Options that are passed down to a browser provider.
|
||||
* To support type hinting, add one of the types to your tsconfig.json "compilerOptions.types" field:
|
||||
*
|
||||
* - for webdriverio: `@vitest/browser/providers/webdriverio`
|
||||
* - for playwright: `@vitest/browser/providers/playwright`
|
||||
*
|
||||
* @example
|
||||
* { playwright: { launch: { devtools: true } }
|
||||
*/
|
||||
providerOptions?: BrowserProviderOptions
|
||||
|
||||
/**
|
||||
* enable headless mode
|
||||
*
|
||||
|
||||
658
pnpm-lock.yaml
generated
658
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ const configs: UserConfig[] = []
|
||||
const pools: UserConfig[] = [{ pool: 'threads' }, { pool: 'forks' }, { pool: 'threads', poolOptions: { threads: { singleThread: true } } }]
|
||||
|
||||
if (process.platform !== 'win32')
|
||||
pools.push({ browser: { enabled: true, name: 'chrome' } })
|
||||
pools.push({ browser: { enabled: true, name: 'chromium', provider: 'playwright' } })
|
||||
|
||||
for (const isolate of [true, false]) {
|
||||
for (const pool of pools) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Vitest } from 'vitest'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import type { VitestWorkspace } from 'vitest/node'
|
||||
import type { WorkspaceProject } from 'vitest/node'
|
||||
import { RandomSequencer } from '../../../packages/vitest/src/node/sequencers/RandomSequencer'
|
||||
import { BaseSequencer } from '../../../packages/vitest/src/node/sequencers/BaseSequencer'
|
||||
import type { WorkspaceSpec } from '../../../packages/vitest/src/node/pool'
|
||||
@ -20,7 +20,7 @@ function buildCtx() {
|
||||
function buildWorkspace() {
|
||||
return {
|
||||
getName: () => 'test',
|
||||
} as any as VitestWorkspace
|
||||
} as any as WorkspaceProject
|
||||
}
|
||||
|
||||
const workspace = buildWorkspace()
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"devDependencies": {
|
||||
"execa": "^6.1.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"playwright-chromium": "^1.27.0",
|
||||
"playwright-chromium": "^1.39.0",
|
||||
"vitest": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user