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(browser): add support for custom browser capabilities #3758

Closed
Closed
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
2 changes: 1 addition & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,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
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!: unknown
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 || undefined), 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!: unknown
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: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are passing down only capabilities? Shouldn't it be remote options?

...(this.options || undefined),
'browserName': this.browser,
'wdio:devtoolsOptions': { headless: options.headless },
'wdio:devtoolsOptions': { ...(this.options && (this.options as any)['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 @@ -285,6 +285,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 @@ -338,11 +338,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?: unknown
playwright?: unknown
}

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