[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.

fix(browser): init browsers eagerly when tests are running (#6876)

authored by

Vladimir and committed by
GitHub
(Nov 13, 2024, 4:03 PM +0100) 417bdb42 32be0af7

+83 -65
+2
docs/guide/browser/index.md
··· 107 107 108 108 ::: info 109 109 Vitest assigns port `63315` to avoid conflicts with the development server, allowing you to run both in parallel. You can change that with the [`browser.api`](/config/#browser-api) option. 110 + 111 + Since Vitest 2.1.5, CLI no longer prints the Vite URL automcatically. You can press "b" to print the URL when running in watch mode. 110 112 ::: 111 113 112 114 If you have not used Vite before, make sure you have your framework's plugin installed and specified in the config. Some frameworks might require extra configuration to work - check their Vite related documentation to be sure.
+1 -4
test/browser/specs/runner.test.ts
··· 1 1 import { readFile } from 'node:fs/promises' 2 2 import { beforeAll, describe, expect, onTestFailed, test } from 'vitest' 3 - import { defaultBrowserPort } from 'vitest/config' 4 - import { browser, provider, runBrowserTests } from './utils' 3 + import { browser, runBrowserTests } from './utils' 5 4 6 5 describe('running browser tests', async () => { 7 6 let stderr: string ··· 28 27 onTestFailed(() => { 29 28 console.error(stderr) 30 29 }) 31 - 32 - expect(stdout).toContain(`Browser runner started by ${provider} at http://localhost:${defaultBrowserPort}/`) 33 30 34 31 expect(browserResultJson.testResults).toHaveLength(19) 35 32 expect(passedTests).toHaveLength(17)
+9 -5
test/browser/specs/server-url.test.ts
··· 1 1 import { afterEach, expect, test } from 'vitest' 2 - import { provider, runBrowserTests } from './utils' 2 + import { runBrowserTests } from './utils' 3 3 4 4 afterEach(() => { 5 5 delete process.env.TEST_HTTPS 6 6 }) 7 7 8 8 test('server-url http', async () => { 9 - const { stdout, stderr } = await runBrowserTests({ 9 + const { stderr, ctx } = await runBrowserTests({ 10 10 root: './fixtures/server-url', 11 + watch: true, // otherwise the browser is closed before we can get the url 11 12 }) 13 + const url = ctx?.projects[0].browser?.vite.resolvedUrls?.local[0] 12 14 expect(stderr).toBe('') 13 - expect(stdout).toContain(`Browser runner started by ${provider} at http://localhost:51133/`) 15 + expect(url).toBe('http://localhost:51133/') 14 16 }) 15 17 16 18 test('server-url https', async () => { 17 19 process.env.TEST_HTTPS = '1' 18 - const { stdout, stderr } = await runBrowserTests({ 20 + const { stdout, stderr, ctx } = await runBrowserTests({ 19 21 root: './fixtures/server-url', 22 + watch: true, // otherwise the browser is closed before we can get the url 20 23 }) 21 24 expect(stderr).toBe('') 22 - expect(stdout).toContain(`Browser runner started by ${provider} at https://localhost:51122/`) 25 + const url = ctx?.projects[0].browser?.vite.resolvedUrls?.local[0] 26 + expect(url).toBe('https://localhost:51122/') 23 27 expect(stdout).toContain('Test Files 1 passed') 24 28 })
+5 -4
test/cli/test/browser-multiple.test.ts
··· 8 8 const workspace = resolve(import.meta.dirname, '../fixtures/browser-multiple/vitest.workspace.ts') 9 9 const spy = vi.spyOn(console, 'log') 10 10 onTestFinished(() => spy.mockRestore()) 11 - const { stderr, stdout } = await runVitest({ 11 + const { stderr, ctx } = await runVitest({ 12 12 root, 13 13 workspace, 14 14 dir: root, 15 - watch: false, 15 + watch: true, 16 16 }) 17 + const urls = ctx?.projects.map(p => p.browser?.vite.resolvedUrls?.local[0]) 17 18 18 19 expect(spy).not.toHaveBeenCalled() 19 20 expect(stderr).not.toContain('is in use, trying another one...') 20 - expect(stdout).toContain('Browser runner started by playwright at http://localhost:63315/') 21 - expect(stdout).toContain('Browser runner started by playwright at http://localhost:63316/') 21 + expect(urls).toContain('http://localhost:63315/') 22 + expect(urls).toContain('http://localhost:63316/') 22 23 })
+4 -9
test/config/test/browser-html.test.ts
··· 5 5 const root = resolve(import.meta.dirname, '../fixtures/browser-custom-html') 6 6 7 7 test('throws an error with non-existing path', async () => { 8 - const { stderr, thrown } = await runVitest({ 8 + const { stderr } = await runVitest({ 9 9 root, 10 10 config: './vitest.config.non-existing.ts', 11 11 }, [], 'test', {}, { fails: true }) 12 - expect(thrown).toBe(true) 13 12 expect(stderr).toContain(`Tester HTML file "${resolve(root, './some-non-existing-path')}" doesn't exist.`) 14 13 }) 15 14 16 15 test('throws an error and exits if there is an error in the html file hook', async () => { 17 - const { stderr, stdout, exitCode } = await runVitest({ 16 + const { stderr, exitCode } = await runVitest({ 18 17 root, 19 18 config: './vitest.config.error-hook.ts', 20 19 }) 21 - expect(stderr).toContain('expected error in transformIndexHtml') 22 - // error happens when browser is opened 23 - expect(stdout).toContain('Browser runner started by playwright') 20 + expect(stderr).toContain('Error: expected error in transformIndexHtml') 21 + expect(stderr).toContain('[vite] Internal server error: expected error in transformIndexHtml') 24 22 expect(exitCode).toBe(1) 25 23 }) 26 24 ··· 31 29 reporters: ['basic'], 32 30 }) 33 31 expect(stderr).toBe('') 34 - expect(stdout).toContain('Browser runner started by playwright') 35 32 expect(stdout).toContain('✓ browser-basic.test.ts') 36 33 expect(exitCode).toBe(0) 37 34 }) ··· 43 40 reporters: ['basic'], 44 41 }) 45 42 expect(stderr).toBe('') 46 - expect(stdout).toContain('Browser runner started by playwright') 47 43 expect(stdout).toContain('✓ browser-custom.test.ts') 48 44 expect(exitCode).toBe(0) 49 45 }) ··· 55 51 reporters: ['basic'], 56 52 }) 57 53 expect(stderr).toBe('') 58 - expect(stdout).toContain('Browser runner started by playwright') 59 54 expect(stdout).toContain('✓ browser-custom.test.ts') 60 55 expect(exitCode).toBe(0) 61 56 })
+1
packages/browser/src/node/pool.ts
··· 131 131 if (isCancelled) { 132 132 break 133 133 } 134 + await project.initBrowserProvider() 134 135 135 136 await executeTests(method, project, files) 136 137 }
+4 -8
packages/vitest/src/node/core.ts
··· 303 303 return this.coverageProvider 304 304 } 305 305 306 - private async initBrowserProviders() { 307 - return Promise.all(this.projects.map(w => w.initBrowserProvider())) 308 - } 309 - 310 306 async mergeReports() { 311 307 if (this.reporters.some(r => r instanceof BlobReporter)) { 312 308 throw new Error('Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.') ··· 369 365 async collect(filters?: string[]) { 370 366 this._onClose = [] 371 367 372 - await this.initBrowserProviders() 373 - 374 368 const files = await this.filterTestsBySource( 375 369 await this.globTestFiles(filters), 376 370 ) ··· 402 396 try { 403 397 await this.initCoverageProvider() 404 398 await this.coverageProvider?.clean(this.config.coverage.clean) 405 - await this.initBrowserProviders() 406 399 } 407 400 finally { 408 401 await this.report('onInit', this) ··· 445 438 try { 446 439 await this.initCoverageProvider() 447 440 await this.coverageProvider?.clean(this.config.coverage.clean) 448 - await this.initBrowserProviders() 449 441 } 450 442 finally { 451 443 await this.report('onInit', this) ··· 691 683 async cancelCurrentRun(reason: CancelReason) { 692 684 this.isCancelling = true 693 685 await Promise.all(this._onCancelListeners.splice(0).map(listener => listener(reason))) 686 + } 687 + 688 + async initBrowserServers() { 689 + await Promise.all(this.projects.map(p => p.initBrowserServer())) 694 690 } 695 691 696 692 async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true) {
+25 -16
packages/vitest/src/node/logger.ts
··· 12 12 import c from 'tinyrainbow' 13 13 import { highlightCode } from '../utils/colors' 14 14 import { printError } from './error' 15 - import { divider, withLabel } from './reporters/renderers/utils' 15 + import { divider, formatProjectName, withLabel } from './reporters/renderers/utils' 16 16 import { RandomSequencer } from './sequencers/RandomSequencer' 17 17 18 18 export interface ErrorOptions { ··· 217 217 this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`)) 218 218 } 219 219 220 - this.ctx.projects.forEach((project) => { 221 - if (!project.browser) { 222 - return 223 - } 224 - const name = project.getName() 225 - const output = project.isCore() ? '' : ` [${name}]` 226 - 227 - const resolvedUrls = project.browser.vite.resolvedUrls 228 - const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] 229 - const provider = project.browser.provider.name 230 - const providerString = provider === 'preview' ? '' : ` by ${provider}` 231 - 232 - this.log(PAD + c.dim(c.green(`${output} Browser runner started${providerString} at ${new URL('/', origin)}`))) 233 - }) 234 - 235 220 if (this.ctx.config.ui) { 236 221 const host = this.ctx.config.api?.host || 'localhost' 237 222 const port = this.ctx.server.config.server.port ··· 258 243 else { 259 244 this.log() 260 245 } 246 + } 247 + 248 + printBrowserBanner(project: WorkspaceProject) { 249 + if (!project.browser) { 250 + return 251 + } 252 + 253 + const resolvedUrls = project.browser.vite.resolvedUrls 254 + const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] 255 + if (!origin) { 256 + return 257 + } 258 + 259 + const name = project.getName() 260 + const output = project.isCore() 261 + ? '' 262 + : formatProjectName(name) 263 + const provider = project.browser.provider.name 264 + const providerString = provider === 'preview' ? '' : ` by ${c.reset(c.bold(provider))}` 265 + this.log( 266 + c.dim( 267 + `${output}Browser runner started${providerString} ${c.dim('at')} ${c.blue(new URL('/', origin))}\n`, 268 + ), 269 + ) 261 270 } 262 271 263 272 printUnhandledErrors(errors: unknown[]) {
+9
packages/vitest/src/node/stdin.ts
··· 18 18 ['p', 'filter by a filename'], 19 19 ['t', 'filter by a test name regex pattern'], 20 20 ['w', 'filter by a project name'], 21 + ['b', 'start the browser server if not started yet'], 21 22 ['q', 'quit'], 22 23 ] 23 24 const cancelKeys = ['space', 'c', 'h', ...keys.map(key => key[0]).flat()] ··· 119 120 // change fileNamePattern 120 121 if (name === 'p') { 121 122 return inputFilePattern() 123 + } 124 + if (name === 'b') { 125 + await ctx.initBrowserServers() 126 + ctx.projects.forEach((project) => { 127 + ctx.logger.log() 128 + ctx.logger.printBrowserBanner(project) 129 + }) 130 + return null 122 131 } 123 132 } 124 133
+8 -10
packages/vitest/src/node/workspace.ts
··· 358 358 return testFiles 359 359 } 360 360 361 - async initBrowserServer(configFile: string | undefined) { 362 - if (!this.isBrowserEnabled()) { 361 + async initBrowserServer() { 362 + if (!this.isBrowserEnabled() || this.browser) { 363 363 return 364 364 } 365 365 await this.ctx.packageInstaller.ensureInstalled('@vitest/browser', this.config.root, this.ctx.version) 366 366 const { createBrowserServer, distRoot } = await import('@vitest/browser') 367 - await this.browser?.close() 368 367 const browser = await createBrowserServer( 369 368 this, 370 - configFile, 369 + this.server.config.configFile, 371 370 [ 372 371 ...MocksPlugins({ 373 372 filter(id) { ··· 408 407 } 409 408 410 409 static async createCoreProject(ctx: Vitest) { 411 - const project = WorkspaceProject.createBasicProject(ctx) 412 - await project.initBrowserServer(ctx.server.config.configFile) 413 - return project 410 + return WorkspaceProject.createBasicProject(ctx) 414 411 } 415 412 416 413 async setServer(options: UserConfig, server: ViteDevServer) { ··· 449 446 return node.resolveId(id, importer) 450 447 }, 451 448 }) 452 - 453 - await this.initBrowserServer(this.server.config.configFile) 454 449 } 455 450 456 451 isBrowserEnabled(): boolean { ··· 495 490 } 496 491 497 492 async initBrowserProvider() { 498 - if (!this.isBrowserEnabled()) { 493 + if (!this.isBrowserEnabled() || this.browser?.provider) { 499 494 return 495 + } 496 + if (!this.browser) { 497 + await this.initBrowserServer() 500 498 } 501 499 await this.browser?.initBrowserProvider() 502 500 }
+3
test/cli/fixtures/browser-multiple/basic.test.js
··· 1 + import { test } from 'vitest'; 2 + 3 + test('passes')
+4 -2
test/cli/fixtures/browser-multiple/vitest.workspace.ts
··· 6 6 cacheDir: resolve(import.meta.dirname, 'basic-1'), 7 7 test: { 8 8 name: 'basic-1', 9 - include: ['none'], 9 + dir: import.meta.dirname, 10 + include: ['./basic.test.js'], 10 11 browser: { 11 12 enabled: true, 12 13 name: 'chromium', ··· 19 20 cacheDir: resolve(import.meta.dirname, 'basic-2'), 20 21 test: { 21 22 name: 'basic-2', 22 - include: ['none'], 23 + dir: import.meta.dirname, 24 + include: ['./basic.test.js'], 23 25 browser: { 24 26 enabled: true, 25 27 name: 'chromium',
+8 -7
packages/browser/src/node/providers/preview.ts
··· 3 3 export class PreviewBrowserProvider implements BrowserProvider { 4 4 public name = 'preview' as const 5 5 public supportsParallelism: boolean = false 6 - private ctx!: WorkspaceProject 6 + private project!: WorkspaceProject 7 7 private open = false 8 8 9 9 getSupportedBrowsers() { ··· 19 19 return {} 20 20 } 21 21 22 - async initialize(ctx: WorkspaceProject) { 23 - this.ctx = ctx 22 + async initialize(project: WorkspaceProject) { 23 + this.project = project 24 24 this.open = false 25 - if (ctx.config.browser.headless) { 25 + if (project.config.browser.headless) { 26 26 throw new Error( 27 27 'You\'ve enabled headless mode for "preview" provider but it doesn\'t support it. Use "playwright" or "webdriverio" instead: https://vitest.dev/guide/browser/#configuration', 28 28 ) 29 29 } 30 + project.ctx.logger.printBrowserBanner(project) 30 31 } 31 32 32 33 async openPage(_contextId: string, url: string) { 33 34 this.open = true 34 - if (!this.ctx.browser) { 35 + if (!this.project.browser) { 35 36 throw new Error('Browser is not initialized') 36 37 } 37 - const options = this.ctx.browser.vite.config.server 38 + const options = this.project.browser.vite.config.server 38 39 const _open = options.open 39 40 options.open = url 40 - this.ctx.browser.vite.openBrowser() 41 + this.project.browser.vite.openBrowser() 41 42 options.open = _open 42 43 } 43 44