[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat(browser): support `--inspect` option in webdriverio (#8613)

authored by

Vladimir and committed by
GitHub
(Sep 23, 2025, 7:38 PM +0200) 38adc86c 0d0e5cdf

+305 -55
+1 -2
test/browser/specs/inspect.test.ts
··· 6 6 7 7 type Message = Partial<InspectorNotification<any>> 8 8 9 - const IS_PLAYWRIGHT = process.env.PROVIDER === 'playwright' 10 9 const REMOTE_DEBUG_URL = '127.0.0.1:9123' 11 10 12 - test.runIf(IS_PLAYWRIGHT || !process.env.CI).each(['', 'with workspace'])('--inspect-brk stops at test file %s', async (isWorkspace) => { 11 + test.each(['', 'with workspace'])('--inspect-brk stops at test file %s', async (isWorkspace) => { 13 12 const options = ['--root', 'fixtures/inspect', '--no-file-parallelism', '--inspect-brk', REMOTE_DEBUG_URL] 14 13 15 14 if (isWorkspace) {
+151
test/config/test/browser-configs.test.ts
··· 3 3 import type { TestFsStructure } from '../../test-utils' 4 4 import crypto from 'node:crypto' 5 5 import { playwright } from '@vitest/browser/providers/playwright' 6 + import { webdriverio } from '@vitest/browser/providers/webdriverio' 7 + import { preview } from '@vitest/browser/src/node/providers/preview.js' 6 8 import { resolve } from 'pathe' 7 9 import { describe, expect, onTestFinished, test } from 'vitest' 8 10 import { createVitest } from 'vitest/node' ··· 383 385 // browser config 384 386 expect(projects[1].config.browser.enabled).toBe(true) 385 387 expect(projects[1].config.browser.headless).toBe(true) 388 + }) 389 + 390 + test('core provider has no options if `provider` is not set', async () => { 391 + const v = await vitest({}, { 392 + browser: { 393 + enabled: true, 394 + instances: [{ 395 + browser: 'chromium', 396 + }], 397 + }, 398 + }) 399 + expect(v.config.browser.provider).toEqual(undefined) 400 + }) 401 + 402 + test('core provider has options if `provider` is playwright', async () => { 403 + const v = await vitest({}, { 404 + browser: { 405 + enabled: true, 406 + provider: playwright({ actionTimeout: 1000 }), 407 + instances: [ 408 + { browser: 'chromium' }, 409 + ], 410 + }, 411 + }) 412 + expect(v.config.browser.provider?.options).toEqual({ 413 + actionTimeout: 1000, 414 + }) 415 + }) 416 + 417 + test('core provider has options if `provider` is wdio', async () => { 418 + const v = await vitest({}, { 419 + browser: { 420 + enabled: true, 421 + provider: webdriverio({ cacheDir: './test' }), 422 + instances: [ 423 + { browser: 'chrome' }, 424 + ], 425 + }, 426 + }) 427 + expect(v.config.browser.provider?.options).toEqual({ 428 + cacheDir: './test', 429 + }) 430 + }) 431 + 432 + test('core provider has options if `provider` is preview', async () => { 433 + const v = await vitest({}, { 434 + browser: { 435 + enabled: true, 436 + provider: preview(), 437 + instances: [ 438 + { browser: 'chrome' }, 439 + ], 440 + }, 441 + }) 442 + expect(v.config.browser.provider?.options).toEqual({}) 443 + }) 444 + 445 + test('provider options can be changed dynamically', async () => { 446 + const v = await vitest({}, { 447 + browser: { 448 + enabled: true, 449 + provider: playwright({ actionTimeout: 1000 }), 450 + instances: [ 451 + { browser: 'chromium' }, 452 + ], 453 + }, 454 + }, { 455 + plugins: [ 456 + { 457 + name: 'update-options', 458 + configureVitest({ project }) { 459 + if (project.config.browser.provider) { 460 + const options = project.config.browser.provider.options as any 461 + options.actionTimeout = 400 462 + options.someDifferentOption = 'test' 463 + } 464 + }, 465 + }, 466 + ], 467 + }) 468 + expect(v.config.browser.provider?.options).toEqual({ 469 + actionTimeout: 400, 470 + someDifferentOption: 'test', 471 + }) 472 + }) 473 + 474 + test('provider options can be changed dynamically if no options are specified', async () => { 475 + const v = await vitest({}, { 476 + browser: { 477 + enabled: true, 478 + provider: playwright(), 479 + instances: [ 480 + { browser: 'chromium' }, 481 + ], 482 + }, 483 + }, { 484 + plugins: [ 485 + { 486 + name: 'update-options', 487 + configureVitest({ project }) { 488 + if (project.config.browser.provider) { 489 + const options = project.config.browser.provider.options as any 490 + options.actionTimeout = 400 491 + options.someDifferentOption = 'test' 492 + } 493 + }, 494 + }, 495 + ], 496 + }) 497 + expect(v.config.browser.provider?.options).toEqual({ 498 + actionTimeout: 400, 499 + someDifferentOption: 'test', 500 + }) 501 + }) 502 + 503 + test('provider options can be changed dynamically in CLI', async () => { 504 + const v = await vitest({ 505 + browser: { 506 + provider: { 507 + name: 'playwright', 508 + _cli: true, 509 + } as any, 510 + instances: [], 511 + }, 512 + }, { 513 + browser: { 514 + enabled: true, 515 + instances: [ 516 + { browser: 'chromium' }, 517 + ], 518 + }, 519 + }, { 520 + plugins: [ 521 + { 522 + name: 'update-options', 523 + configureVitest({ project }) { 524 + if (project.config.browser.provider) { 525 + const options = project.config.browser.provider.options as any 526 + options.actionTimeout = 400 527 + options.someDifferentOption = 'test' 528 + } 529 + }, 530 + }, 531 + ], 532 + }) 533 + expect(v.config.browser.provider?.options).toEqual({ 534 + actionTimeout: 400, 535 + someDifferentOption: 'test', 536 + }) 386 537 }) 387 538 388 539 function getCliConfig(options: TestUserConfig, cli: string[], fs: TestFsStructure = {}) {
+34 -27
test/config/test/failures.test.ts
··· 107 107 if (provider.name === 'playwright' && name === 'chromium') { 108 108 continue 109 109 } 110 + if (provider.name === 'webdriverio' && (name === 'chrome' || name === 'edge')) { 111 + continue 112 + } 110 113 111 114 const { stderr } = await runVitest({}, { 112 115 test: { ··· 134 137 Use either: 135 138 { 136 139 browser: { 137 - provider: playwright(), 140 + provider: ${provider.name === 'preview' ? 'playwright' : provider.name}(), 138 141 instances: [ 139 - { browser: 'chromium' } 142 + { browser: '${(provider.name === 'preview' || provider.name === 'playwright') ? 'chromium' : 'chrome'}' } 140 143 ], 141 144 }, 142 145 } ··· 148 151 } 149 152 }) 150 153 151 - test('v8 coverage provider throws when not playwright + chromium', async () => { 152 - for (const { provider, name } of browsers) { 153 - if (provider.name === 'playwright' && name === 'chromium') { 154 - continue 154 + test.each( 155 + browsers.filter(({ provider, name }) => { 156 + if (provider.name === 'playwright') { 157 + return name !== 'chromium' 155 158 } 156 - 157 - const { stderr } = await runVitest({}, { 158 - test: { 159 - coverage: { 160 - enabled: true, 161 - }, 162 - browser: { 163 - enabled: true, 164 - provider, 165 - instances: [{ browser: name }], 166 - }, 159 + if (provider.name === 'webdriverio') { 160 + return name !== 'chrome' && name !== 'edge' 161 + } 162 + return true 163 + }), 164 + )('v8 coverage provider throws when $provider.name + $name', async ({ provider, name }) => { 165 + const { stderr } = await runVitest({}, { 166 + test: { 167 + coverage: { 168 + enabled: true, 167 169 }, 168 - }) 170 + browser: { 171 + enabled: true, 172 + provider, 173 + instances: [{ browser: name }], 174 + }, 175 + }, 176 + }) 169 177 170 - expect(stderr).toMatch( 171 - `Error: @vitest/coverage-v8 does not work with 178 + expect(stderr).toMatch( 179 + `Error: @vitest/coverage-v8 does not work with 172 180 { 173 181 browser: { 174 182 provider: ${provider.name}(), ··· 181 189 Use either: 182 190 { 183 191 browser: { 184 - provider: playwright(), 192 + provider: ${provider.name === 'preview' ? 'playwright' : provider.name}(), 185 193 instances: [ 186 - { browser: 'chromium' } 194 + { browser: '${(provider.name === 'preview' || provider.name === 'playwright') ? 'chromium' : 'chrome'}' } 187 195 ], 188 196 }, 189 197 } ··· 195 203 }, 196 204 } 197 205 `, 198 - ) 199 - } 206 + ) 200 207 }) 201 208 202 209 test('v8 coverage provider throws when using chromium and other non-chromium browser', async () => { ··· 250 257 ) 251 258 }) 252 259 253 - test('v8 coverage provider cannot be used in workspace without playwright + chromium', async () => { 260 + test('v8 coverage provider cannot be used in workspace without chromium', async () => { 254 261 const { stderr } = await runVitest({ 255 262 coverage: { enabled: true }, 256 263 projects: [ ··· 260 267 browser: { 261 268 enabled: true, 262 269 provider: webdriverio(), 263 - instances: [{ browser: 'chrome' }], 270 + instances: [{ browser: 'webkit' }], 264 271 }, 265 272 }, 266 273 }, ··· 272 279 browser: { 273 280 provider: webdriverio(), 274 281 instances: [ 275 - { browser: 'chrome' } 282 + { browser: 'webkit' } 276 283 ], 277 284 }, 278 285 }`,
+13
packages/browser/src/node/pool.ts
··· 356 356 } 357 357 358 358 const provider = this.project.browser!.provider 359 + const browser = this.project.config.browser.name 360 + 361 + if (shouldIgnoreDebugger(provider.name, browser)) { 362 + debug?.('[$s] ignoring debugger in %s browser because it is not supported', sessionId, browser) 363 + return 364 + } 359 365 360 366 if (!provider.getCDPSession) { 361 367 throw new Error('Unable to set breakpoint, CDP not supported') ··· 369 375 urlRegex: escapePathToRegexp(file), 370 376 }) 371 377 } 378 + } 379 + 380 + function shouldIgnoreDebugger(provider: string, browser: string) { 381 + if (provider === 'webdriverio') { 382 + return browser !== 'chrome' && browser !== 'edge' 383 + } 384 + return browser !== 'chromium' 372 385 }
+5 -3
test/browser/fixtures/inspect/vitest.config.ts
··· 1 - import { playwright } from '@vitest/browser/providers/playwright'; 1 + import { provider } from '../../settings' 2 2 import { defineConfig } from 'vitest/config'; 3 3 4 4 export default defineConfig({ ··· 7 7 watch: false, 8 8 browser: { 9 9 enabled: true, 10 - provider: playwright(), 11 - instances: [{ browser: "chromium" }], 10 + provider, 11 + instances: [ 12 + { browser: provider.name === 'webdriverio' ? "chrome" : "chromium" }, 13 + ], 12 14 headless: true, 13 15 }, 14 16 },
+5 -3
test/browser/fixtures/inspect/vitest.config.with-workspace.ts
··· 1 - import { playwright } from '@vitest/browser/providers/playwright'; 2 1 import { defineConfig } from "vitest/config"; 2 + import { provider } from '../../settings' 3 3 4 4 export default defineConfig({ 5 5 server: { port: 5199 }, ··· 11 11 test: { 12 12 name: "Browser in workspace", 13 13 browser: { 14 - provider: playwright(), 14 + provider, 15 15 enabled: true, 16 16 headless: true, 17 - instances: [{ browser: "chromium" }] 17 + instances: [ 18 + { browser: provider.name === 'webdriverio' ? "chrome" : "chromium" }, 19 + ], 18 20 }, 19 21 }, 20 22 },
+6 -5
packages/browser/src/node/providers/playwright.ts
··· 112 112 } 113 113 const promises = [] 114 114 for (const [trace, contextId] of this.pendingTraces.entries()) { 115 - promises.push(() => { 115 + promises.push((() => { 116 116 const context = this.contexts.get(contextId) 117 117 return context?.tracing.stopChunk({ path: trace }) 118 - }) 118 + })()) 119 119 } 120 120 return Promise.allSettled(promises) 121 121 }) ··· 162 162 launchOptions.tracesDir = options.trace?.tracesDir 163 163 } 164 164 165 - if (this.project.config.inspector.enabled) { 165 + const inspector = this.project.vitest.config.inspector 166 + if (inspector.enabled) { 166 167 // NodeJS equivalent defaults: https://nodejs.org/en/learn/getting-started/debugging#enable-inspector 167 - const port = this.project.config.inspector.port || 9229 168 - const host = this.project.config.inspector.host || '127.0.0.1' 168 + const port = inspector.port || 9229 169 + const host = inspector.host || '127.0.0.1' 169 170 170 171 launchOptions.args ||= [] 171 172 launchOptions.args.push(`--remote-debugging-port=${port}`)
+43
packages/browser/src/node/providers/webdriverio.ts
··· 6 6 import type { 7 7 BrowserProvider, 8 8 BrowserProviderOption, 9 + CDPSession, 9 10 TestProject, 10 11 } from 'vitest/node' 11 12 import type { ClickOptions, DragAndDropOptions, remote } from 'webdriverio' ··· 191 192 capabilities[key]!.args = args 192 193 } 193 194 195 + const inspector = this.project.vitest.config.inspector 196 + if (inspector.enabled && (browser === 'chrome' || browser === 'edge')) { 197 + const key = browser === 'chrome' 198 + ? 'goog:chromeOptions' 199 + : 'ms:edgeOptions' 200 + const args = capabilities[key]?.args || [] 201 + 202 + // NodeJS equivalent defaults: https://nodejs.org/en/learn/getting-started/debugging#enable-inspector 203 + const port = inspector.port || 9229 204 + const host = inspector.host || '127.0.0.1' 205 + 206 + args.push(`--remote-debugging-port=${port}`) 207 + args.push(`--remote-debugging-address=${host}`) 208 + 209 + this.project.vitest.logger.log(`Debugger listening on ws://${host}:${port}`) 210 + 211 + capabilities[key] ??= {} 212 + capabilities[key]!.args = args 213 + } 214 + 194 215 return capabilities 195 216 } 196 217 ··· 225 246 await browser.deleteSession() 226 247 browser.sessionId = undefined as unknown as string 227 248 this.browser = null 249 + } 250 + 251 + async getCDPSession(_sessionId: string): Promise<CDPSession> { 252 + return { 253 + send: (method: string, params: any) => { 254 + if (!this.browser) { 255 + throw new Error(`The environment was torn down.`) 256 + } 257 + return this.browser.sendCommandAndGetResult(method, params ?? {}).catch((error) => { 258 + return Promise.reject(new Error(`Failed to execute "${method}" command.`, { cause: error })) 259 + }) 260 + }, 261 + on: () => { 262 + throw new Error(`webdriverio provider doesn't support cdp.on()`) 263 + }, 264 + once: () => { 265 + throw new Error(`webdriverio provider doesn't support cdp.once()`) 266 + }, 267 + off: () => { 268 + throw new Error(`webdriverio provider doesn't support cdp.off()`) 269 + }, 270 + } 228 271 } 229 272 } 230 273
+47 -15
packages/vitest/src/node/config/resolveConfig.ts
··· 271 271 } 272 272 } 273 273 274 - const playwrightChromiumOnly = isPlaywrightChromiumOnly(vitest, resolved) 274 + const containsChromium = hasBrowserChromium(vitest, resolved) 275 + const hasOnlyChromium = hasOnlyBrowserChromium(vitest, resolved) 275 276 276 - // Browser-mode "Playwright + Chromium" only features: 277 - if (browser.enabled && !playwrightChromiumOnly) { 277 + // Browser-mode "Chromium" only features: 278 + if (browser.enabled && (!containsChromium || !hasOnlyChromium)) { 278 279 const browserConfig = ` 279 280 { 280 281 browser: { ··· 286 287 } 287 288 `.trim() 288 289 290 + const preferredProvider = (!browser.provider?.name || browser.provider.name === 'preview') 291 + ? 'playwright' 292 + : browser.provider.name 293 + const preferredBrowser = preferredProvider === 'playwright' ? 'chromium' : 'chrome' 289 294 const correctExample = ` 290 295 { 291 296 browser: { 292 - provider: playwright(), 297 + provider: ${preferredProvider}(), 293 298 instances: [ 294 - { browser: 'chromium' } 299 + { browser: '${preferredBrowser}' } 295 300 ], 296 301 }, 297 302 } 298 303 `.trim() 299 304 300 - if (resolved.coverage.enabled && resolved.coverage.provider === 'v8') { 305 + // requires all projects to be chromium 306 + if (!hasOnlyChromium && resolved.coverage.enabled && resolved.coverage.provider === 'v8') { 301 307 const coverageExample = ` 302 308 { 303 309 coverage: { ··· 313 319 ) 314 320 } 315 321 316 - if (resolved.inspect || resolved.inspectBrk) { 322 + // ignores non-chromium browsers when there is at least one chromium project 323 + if (!containsChromium && (resolved.inspect || resolved.inspectBrk)) { 317 324 const inspectOption = `--inspect${resolved.inspectBrk ? '-brk' : ''}` 318 325 319 326 throw new Error( ··· 846 853 return resolvedReporters 847 854 } 848 855 849 - function isPlaywrightChromiumOnly(vitest: Vitest, config: ResolvedConfig) { 856 + function isChromiumName(provider: string, name: string) { 857 + if (provider === 'playwright') { 858 + return name === 'chromium' 859 + } 860 + return name === 'chrome' || name === 'edge' 861 + } 862 + 863 + function hasBrowserChromium(vitest: Vitest, config: ResolvedConfig) { 850 864 const browser = config.browser 851 - if (!browser || !browser.provider || browser.provider.name !== 'playwright' || !browser.enabled) { 865 + if (!browser || !browser.provider || browser.provider.name === 'preview' || !browser.enabled) { 852 866 return false 853 867 } 854 868 if (browser.name) { 855 - return browser.name === 'chromium' 869 + return isChromiumName(browser.provider.name, browser.name) 856 870 } 857 871 if (!browser.instances) { 858 872 return false 859 873 } 860 - for (const instance of browser.instances) { 874 + return browser.instances.some((instance) => { 861 875 const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser) 862 876 // browser config is filtered out 863 877 if (!vitest.matchesProjectFilter(name)) { 864 - continue 865 - } 866 - if (instance.browser !== 'chromium') { 867 878 return false 868 879 } 880 + return isChromiumName(browser.provider!.name, instance.browser) 881 + }) 882 + } 883 + 884 + function hasOnlyBrowserChromium(vitest: Vitest, config: ResolvedConfig) { 885 + const browser = config.browser 886 + if (!browser || !browser.provider || browser.provider.name === 'preview' || !browser.enabled) { 887 + return false 869 888 } 870 - return true 889 + if (browser.name) { 890 + return isChromiumName(browser.provider.name, browser.name) 891 + } 892 + if (!browser.instances) { 893 + return false 894 + } 895 + return browser.instances.every((instance) => { 896 + const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser) 897 + // browser config is filtered out 898 + if (!vitest.matchesProjectFilter(name)) { 899 + return true // ignore this project 900 + } 901 + return isChromiumName(browser.provider!.name, instance.browser) 902 + }) 871 903 }