Skip to content

Commit

Permalink
feat!: add support for browser provider capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
haywirez committed Jul 13, 2023
1 parent a90d64f commit dfc9894
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 37 deletions.
2 changes: 1 addition & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,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?: ProviderSpecificOptions }): Awaitable<void>
openPage(url: string): Awaitable<void>
close(): Awaitable<void>
}
Expand Down
43 changes: 43 additions & 0 deletions docs/guide/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,50 @@ npx vitest --browser.name=chrome --browser.headless

In this case, Vitest will run in headless mode using the Chrome browser.

## Custom Provider Options

You may pass provider-specific options, such as [WebdriverIO custom capabilities](https://webdriver.io/docs/capabilities#custom-capabilities) like:

```ts
export default defineConfig({
test: {
browser: {
enabled: true,
headless: true,
name: 'webdriverio',
options: {
webdriverio: {
'goog:chromeOptions': {
args: ['disable-gpu'],
},
},
},
},
},
})
```

or in case of [Playwright-specific options](https://playwright.dev/docs/api/class-browsertype#browser-type-launch):

```ts
export default defineConfig({
test: {
browser: {
enabled: true,
headless: true,
name: 'playwright',
options: {
playwright: {
args: ['disable-gpu'], // chromium
},
},
},
},
})
```

## Limitations

### Thread Blocking Dialogs

When using Vitest Browser, it's important to note that thread blocking dialogs like `alert` or `confirm` cannot be used natively. This is because they block the web page, which means Vitest cannot continue communicating with the page, causing the execution to hang.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@vitest/coverage-istanbul": "workspace:*",
"@vitest/coverage-v8": "workspace:*",
"@vitest/ui": "workspace:*",
"@wdio/types": "^8.10.4",
"bumpp": "^9.1.1",
"esbuild": "^0.18.11",
"eslint": "^8.44.0",
Expand Down
9 changes: 6 additions & 3 deletions packages/vitest/src/node/browser/playwright.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Page } from 'playwright'
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
import type { BrowserProvider, BrowserProviderOptions, ProviderSpecificOptions } from '../../types/browser'
import { ensurePackageInstalled } from '../pkg'
import type { WorkspaceProject } from '../workspace'
import type { Awaitable } from '../../types'
Expand All @@ -9,22 +9,25 @@ export type PlaywrightBrowser = typeof playwrightBrowsers[number]

export interface PlaywrightProviderOptions extends BrowserProviderOptions {
browser: PlaywrightBrowser
options: ProviderSpecificOptions
}

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

private cachedBrowser: Page | null = null
private browser!: PlaywrightBrowser
private options!: any
private ctx!: WorkspaceProject

getSupportedBrowsers() {
return playwrightBrowsers
}

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

const root = this.ctx.config.root

Expand All @@ -40,7 +43,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider {

const playwright = await import('playwright')

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

this.cachedBrowser.on('close', () => {
Expand Down
10 changes: 7 additions & 3 deletions packages/vitest/src/node/browser/webdriver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Awaitable } from '@vitest/utils'
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
import type { BrowserProvider, BrowserProviderOptions, ProviderSpecificOptions } from '../../types/browser'
import { ensurePackageInstalled } from '../pkg'
import type { WorkspaceProject } from '../workspace'

Expand All @@ -8,6 +8,7 @@ export type WebdriverBrowser = typeof webdriverBrowsers[number]

export interface WebdriverProviderOptions extends BrowserProviderOptions {
browser: WebdriverBrowser
options: ProviderSpecificOptions
}

export class WebdriverBrowserProvider implements BrowserProvider {
Expand All @@ -16,15 +17,17 @@ export class WebdriverBrowserProvider implements BrowserProvider {
private cachedBrowser: WebdriverIO.Browser | null = null
private stopSafari: () => void = () => {}
private browser!: WebdriverBrowser
private options!: WebDriver.Capabilities | WebDriver.DesiredCapabilities | undefined
private ctx!: WorkspaceProject

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

const root = this.ctx.config.root

Expand Down Expand Up @@ -57,8 +60,9 @@ export class WebdriverBrowserProvider implements BrowserProvider {
this.cachedBrowser = await remote({
logLevel: 'error',
capabilities: {
...this.options,
'browserName': this.browser,
'wdio:devtoolsOptions': { headless: options.headless },
'wdio:devtoolsOptions': { ...this.options && this.options['wdio:devtoolsOptions'], headless: options.headless },
},
})

Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export function resolveConfig(

resolved.browser ??= {} as any
resolved.browser.enabled ??= false
resolved.browser.options ??= {}
resolved.browser.headless ??= isCI
resolved.browser.slowHijackESM ??= true

Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,12 @@ export class WorkspaceProject {
const Provider = await getBrowserProvider(this.config.browser, this.runner)
this.browserProvider = new Provider()
const browser = this.config.browser.name
const options = this.config.browser.options
const supportedBrowsers = this.browserProvider.getSupportedBrowsers()
if (!browser)
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 })
await this.browserProvider.initialize(this, { browser, options })
}
}
17 changes: 16 additions & 1 deletion packages/vitest/src/types/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import type { Awaitable } from '@vitest/utils'
import type { WorkspaceProject } from '../node/workspace'
import type { ApiConfig } from './config'

export interface ProviderSpecificOptions {
webdriverio?: WebDriver.Capabilities | WebDriver.DesiredCapabilities
playwright?: any
}

export interface BrowserProviderOptions {
browser: string
options?: ProviderSpecificOptions
}

export interface BrowserProvider {
name: string
getSupportedBrowsers(): readonly string[]
initialize(ctx: WorkspaceProject, options: BrowserProviderOptions): Awaitable<void>
initialize(
ctx: WorkspaceProject,
options: BrowserProviderOptions
): Awaitable<void>
openPage(url: string): Awaitable<void>
catchError(cb: (error: Error) => Awaitable<void>): () => Awaitable<void>
close(): Awaitable<void>
Expand Down Expand Up @@ -61,6 +70,12 @@ export interface BrowserConfigOptions {
* @experimental
*/
slowHijackESM?: boolean

/**
* Custom provider/capabilities options passed on for the specific provider.
*
*/
options?: ProviderSpecificOptions | {}
}

export interface ResolvedBrowserOptions extends BrowserConfigOptions {
Expand Down
Loading

0 comments on commit dfc9894

Please sign in to comment.