diff --git a/docs/config/index.md b/docs/config/index.md index 73131c2c866c..0d8b4e2eb335 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -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 + initialize(ctx: Vitest, options: { browser: string; options?: ProviderSpecificOptions }): Awaitable openPage(url: string): Awaitable close(): Awaitable } diff --git a/docs/guide/browser.md b/docs/guide/browser.md index 161814a855c0..4d6ea9d4feef 100644 --- a/docs/guide/browser.md +++ b/docs/guide/browser.md @@ -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. diff --git a/packages/vitest/src/node/browser/playwright.ts b/packages/vitest/src/node/browser/playwright.ts index 3c8feb3f121d..926f143b9e8f 100644 --- a/packages/vitest/src/node/browser/playwright.ts +++ b/packages/vitest/src/node/browser/playwright.ts @@ -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' @@ -9,6 +9,7 @@ export type PlaywrightBrowser = typeof playwrightBrowsers[number] export interface PlaywrightProviderOptions extends BrowserProviderOptions { browser: PlaywrightBrowser + options: ProviderSpecificOptions } export class PlaywrightBrowserProvider implements BrowserProvider { @@ -16,15 +17,17 @@ export class PlaywrightBrowserProvider implements BrowserProvider { 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 @@ -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', () => { diff --git a/packages/vitest/src/node/browser/webdriver.ts b/packages/vitest/src/node/browser/webdriver.ts index 29aa8a2f959c..3f0ecbab22a7 100644 --- a/packages/vitest/src/node/browser/webdriver.ts +++ b/packages/vitest/src/node/browser/webdriver.ts @@ -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' @@ -8,6 +8,7 @@ export type WebdriverBrowser = typeof webdriverBrowsers[number] export interface WebdriverProviderOptions extends BrowserProviderOptions { browser: WebdriverBrowser + options: ProviderSpecificOptions } export class WebdriverBrowserProvider implements BrowserProvider { @@ -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 @@ -57,8 +60,9 @@ export class WebdriverBrowserProvider implements BrowserProvider { this.cachedBrowser = await remote({ logLevel: 'error', capabilities: { + ...(this.options || undefined), 'browserName': this.browser, - 'wdio:devtoolsOptions': { headless: options.headless }, + 'wdio:devtoolsOptions': { ...(this.options && (this.options as any)['wdio:devtoolsOptions']), headless: options.headless }, }, }) diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 087076ac6375..8d95318da441 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -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 diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 120c8f20ccee..59ee6cdcb96d 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -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 }) } } diff --git a/packages/vitest/src/types/browser.ts b/packages/vitest/src/types/browser.ts index 9cc8616cf863..e265cd1d0c99 100644 --- a/packages/vitest/src/types/browser.ts +++ b/packages/vitest/src/types/browser.ts @@ -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 + initialize( + ctx: WorkspaceProject, + options: BrowserProviderOptions + ): Awaitable openPage(url: string): Awaitable catchError(cb: (error: Error) => Awaitable): () => Awaitable close(): Awaitable @@ -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 {