diff --git a/docs/guide/browser/index.md b/docs/guide/browser/index.md index 14dc37749b7f..87d5ca5d398e 100644 --- a/docs/guide/browser/index.md +++ b/docs/guide/browser/index.md @@ -107,6 +107,8 @@ export default defineConfig({ ::: info 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. + +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. ::: 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. diff --git a/packages/browser/src/node/pool.ts b/packages/browser/src/node/pool.ts index bd7399a42dee..9079535bee75 100644 --- a/packages/browser/src/node/pool.ts +++ b/packages/browser/src/node/pool.ts @@ -131,6 +131,7 @@ export function createBrowserPool(ctx: Vitest): ProcessPool { if (isCancelled) { break } + await project.initBrowserProvider() await executeTests(method, project, files) } diff --git a/packages/browser/src/node/providers/preview.ts b/packages/browser/src/node/providers/preview.ts index 8693f661d6fa..a8e8829a7cfd 100644 --- a/packages/browser/src/node/providers/preview.ts +++ b/packages/browser/src/node/providers/preview.ts @@ -3,7 +3,7 @@ import type { BrowserProvider, WorkspaceProject } from 'vitest/node' export class PreviewBrowserProvider implements BrowserProvider { public name = 'preview' as const public supportsParallelism: boolean = false - private ctx!: WorkspaceProject + private project!: WorkspaceProject private open = false getSupportedBrowsers() { @@ -19,25 +19,26 @@ export class PreviewBrowserProvider implements BrowserProvider { return {} } - async initialize(ctx: WorkspaceProject) { - this.ctx = ctx + async initialize(project: WorkspaceProject) { + this.project = project this.open = false - if (ctx.config.browser.headless) { + if (project.config.browser.headless) { throw new Error( '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', ) } + project.ctx.logger.printBrowserBanner(project) } async openPage(_contextId: string, url: string) { this.open = true - if (!this.ctx.browser) { + if (!this.project.browser) { throw new Error('Browser is not initialized') } - const options = this.ctx.browser.vite.config.server + const options = this.project.browser.vite.config.server const _open = options.open options.open = url - this.ctx.browser.vite.openBrowser() + this.project.browser.vite.openBrowser() options.open = _open } diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 0d885bd58fe9..74fcceda3874 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -303,10 +303,6 @@ export class Vitest { return this.coverageProvider } - private async initBrowserProviders() { - return Promise.all(this.projects.map(w => w.initBrowserProvider())) - } - async mergeReports() { if (this.reporters.some(r => r instanceof BlobReporter)) { throw new Error('Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.') @@ -369,8 +365,6 @@ export class Vitest { async collect(filters?: string[]) { this._onClose = [] - await this.initBrowserProviders() - const files = await this.filterTestsBySource( await this.globTestFiles(filters), ) @@ -402,7 +396,6 @@ export class Vitest { try { await this.initCoverageProvider() await this.coverageProvider?.clean(this.config.coverage.clean) - await this.initBrowserProviders() } finally { await this.report('onInit', this) @@ -445,7 +438,6 @@ export class Vitest { try { await this.initCoverageProvider() await this.coverageProvider?.clean(this.config.coverage.clean) - await this.initBrowserProviders() } finally { await this.report('onInit', this) @@ -693,6 +685,10 @@ export class Vitest { await Promise.all(this._onCancelListeners.splice(0).map(listener => listener(reason))) } + async initBrowserServers() { + await Promise.all(this.projects.map(p => p.initBrowserServer())) + } + async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true) { if (this.filenamePattern) { const filteredFiles = await this.globTestFiles([this.filenamePattern]) diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 7ac51abb63bb..5254bf6b3d4e 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -12,7 +12,7 @@ import { createLogUpdate } from 'log-update' import c from 'tinyrainbow' import { highlightCode } from '../utils/colors' import { printError } from './error' -import { divider, withLabel } from './reporters/renderers/utils' +import { divider, formatProjectName, withLabel } from './reporters/renderers/utils' import { RandomSequencer } from './sequencers/RandomSequencer' export interface ErrorOptions { @@ -217,21 +217,6 @@ export class Logger { this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`)) } - this.ctx.projects.forEach((project) => { - if (!project.browser) { - return - } - const name = project.getName() - const output = project.isCore() ? '' : ` [${name}]` - - const resolvedUrls = project.browser.vite.resolvedUrls - const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] - const provider = project.browser.provider.name - const providerString = provider === 'preview' ? '' : ` by ${provider}` - - this.log(PAD + c.dim(c.green(`${output} Browser runner started${providerString} at ${new URL('/', origin)}`))) - }) - if (this.ctx.config.ui) { const host = this.ctx.config.api?.host || 'localhost' const port = this.ctx.server.config.server.port @@ -260,6 +245,30 @@ export class Logger { } } + printBrowserBanner(project: WorkspaceProject) { + if (!project.browser) { + return + } + + const resolvedUrls = project.browser.vite.resolvedUrls + const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] + if (!origin) { + return + } + + const name = project.getName() + const output = project.isCore() + ? '' + : formatProjectName(name) + const provider = project.browser.provider.name + const providerString = provider === 'preview' ? '' : ` by ${c.reset(c.bold(provider))}` + this.log( + c.dim( + `${output}Browser runner started${providerString} ${c.dim('at')} ${c.blue(new URL('/', origin))}\n`, + ), + ) + } + printUnhandledErrors(errors: unknown[]) { const errorMessage = c.red( c.bold( diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index 889439595e1e..705980b1ecb3 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -18,6 +18,7 @@ const keys = [ ['p', 'filter by a filename'], ['t', 'filter by a test name regex pattern'], ['w', 'filter by a project name'], + ['b', 'start the browser server if not started yet'], ['q', 'quit'], ] const cancelKeys = ['space', 'c', 'h', ...keys.map(key => key[0]).flat()] @@ -120,6 +121,14 @@ export function registerConsoleShortcuts( if (name === 'p') { return inputFilePattern() } + if (name === 'b') { + await ctx.initBrowserServers() + ctx.projects.forEach((project) => { + ctx.logger.log() + ctx.logger.printBrowserBanner(project) + }) + return null + } } async function keypressHandler(str: string, key: any) { diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index d60ba846853c..76e87a144f63 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -358,16 +358,15 @@ export class WorkspaceProject { return testFiles } - async initBrowserServer(configFile: string | undefined) { - if (!this.isBrowserEnabled()) { + async initBrowserServer() { + if (!this.isBrowserEnabled() || this.browser) { return } await this.ctx.packageInstaller.ensureInstalled('@vitest/browser', this.config.root, this.ctx.version) const { createBrowserServer, distRoot } = await import('@vitest/browser') - await this.browser?.close() const browser = await createBrowserServer( this, - configFile, + this.server.config.configFile, [ ...MocksPlugins({ filter(id) { @@ -408,9 +407,7 @@ export class WorkspaceProject { } static async createCoreProject(ctx: Vitest) { - const project = WorkspaceProject.createBasicProject(ctx) - await project.initBrowserServer(ctx.server.config.configFile) - return project + return WorkspaceProject.createBasicProject(ctx) } async setServer(options: UserConfig, server: ViteDevServer) { @@ -449,8 +446,6 @@ export class WorkspaceProject { return node.resolveId(id, importer) }, }) - - await this.initBrowserServer(this.server.config.configFile) } isBrowserEnabled(): boolean { @@ -495,9 +490,12 @@ export class WorkspaceProject { } async initBrowserProvider() { - if (!this.isBrowserEnabled()) { + if (!this.isBrowserEnabled() || this.browser?.provider) { return } + if (!this.browser) { + await this.initBrowserServer() + } await this.browser?.initBrowserProvider() } } diff --git a/test/browser/specs/runner.test.ts b/test/browser/specs/runner.test.ts index 64b8a94fc632..545ad88116d9 100644 --- a/test/browser/specs/runner.test.ts +++ b/test/browser/specs/runner.test.ts @@ -1,7 +1,6 @@ import { readFile } from 'node:fs/promises' import { beforeAll, describe, expect, onTestFailed, test } from 'vitest' -import { defaultBrowserPort } from 'vitest/config' -import { browser, provider, runBrowserTests } from './utils' +import { browser, runBrowserTests } from './utils' describe('running browser tests', async () => { let stderr: string @@ -29,8 +28,6 @@ describe('running browser tests', async () => { console.error(stderr) }) - expect(stdout).toContain(`Browser runner started by ${provider} at http://localhost:${defaultBrowserPort}/`) - expect(browserResultJson.testResults).toHaveLength(19) expect(passedTests).toHaveLength(17) expect(failedTests).toHaveLength(2) diff --git a/test/browser/specs/server-url.test.ts b/test/browser/specs/server-url.test.ts index 84f544cf5bd7..153cbd1cba55 100644 --- a/test/browser/specs/server-url.test.ts +++ b/test/browser/specs/server-url.test.ts @@ -1,24 +1,28 @@ import { afterEach, expect, test } from 'vitest' -import { provider, runBrowserTests } from './utils' +import { runBrowserTests } from './utils' afterEach(() => { delete process.env.TEST_HTTPS }) test('server-url http', async () => { - const { stdout, stderr } = await runBrowserTests({ + const { stderr, ctx } = await runBrowserTests({ root: './fixtures/server-url', + watch: true, // otherwise the browser is closed before we can get the url }) + const url = ctx?.projects[0].browser?.vite.resolvedUrls?.local[0] expect(stderr).toBe('') - expect(stdout).toContain(`Browser runner started by ${provider} at http://localhost:51133/`) + expect(url).toBe('http://localhost:51133/') }) test('server-url https', async () => { process.env.TEST_HTTPS = '1' - const { stdout, stderr } = await runBrowserTests({ + const { stdout, stderr, ctx } = await runBrowserTests({ root: './fixtures/server-url', + watch: true, // otherwise the browser is closed before we can get the url }) expect(stderr).toBe('') - expect(stdout).toContain(`Browser runner started by ${provider} at https://localhost:51122/`) + const url = ctx?.projects[0].browser?.vite.resolvedUrls?.local[0] + expect(url).toBe('https://localhost:51122/') expect(stdout).toContain('Test Files 1 passed') }) diff --git a/test/cli/fixtures/browser-multiple/basic.test.js b/test/cli/fixtures/browser-multiple/basic.test.js new file mode 100644 index 000000000000..5679012438a1 --- /dev/null +++ b/test/cli/fixtures/browser-multiple/basic.test.js @@ -0,0 +1,3 @@ +import { test } from 'vitest'; + +test('passes') diff --git a/test/cli/fixtures/browser-multiple/vitest.workspace.ts b/test/cli/fixtures/browser-multiple/vitest.workspace.ts index 4bd2b203b471..8ed483f8e423 100644 --- a/test/cli/fixtures/browser-multiple/vitest.workspace.ts +++ b/test/cli/fixtures/browser-multiple/vitest.workspace.ts @@ -6,7 +6,8 @@ export default defineWorkspace([ cacheDir: resolve(import.meta.dirname, 'basic-1'), test: { name: 'basic-1', - include: ['none'], + dir: import.meta.dirname, + include: ['./basic.test.js'], browser: { enabled: true, name: 'chromium', @@ -19,7 +20,8 @@ export default defineWorkspace([ cacheDir: resolve(import.meta.dirname, 'basic-2'), test: { name: 'basic-2', - include: ['none'], + dir: import.meta.dirname, + include: ['./basic.test.js'], browser: { enabled: true, name: 'chromium', diff --git a/test/cli/test/browser-multiple.test.ts b/test/cli/test/browser-multiple.test.ts index a26ca1263253..3da5e1bf439e 100644 --- a/test/cli/test/browser-multiple.test.ts +++ b/test/cli/test/browser-multiple.test.ts @@ -8,15 +8,16 @@ it('automatically assigns the port', async () => { const workspace = resolve(import.meta.dirname, '../fixtures/browser-multiple/vitest.workspace.ts') const spy = vi.spyOn(console, 'log') onTestFinished(() => spy.mockRestore()) - const { stderr, stdout } = await runVitest({ + const { stderr, ctx } = await runVitest({ root, workspace, dir: root, - watch: false, + watch: true, }) + const urls = ctx?.projects.map(p => p.browser?.vite.resolvedUrls?.local[0]) expect(spy).not.toHaveBeenCalled() expect(stderr).not.toContain('is in use, trying another one...') - expect(stdout).toContain('Browser runner started by playwright at http://localhost:63315/') - expect(stdout).toContain('Browser runner started by playwright at http://localhost:63316/') + expect(urls).toContain('http://localhost:63315/') + expect(urls).toContain('http://localhost:63316/') }) diff --git a/test/config/test/browser-html.test.ts b/test/config/test/browser-html.test.ts index 58812f856c07..19abfc738cdc 100644 --- a/test/config/test/browser-html.test.ts +++ b/test/config/test/browser-html.test.ts @@ -5,22 +5,20 @@ import { runVitest } from '../../test-utils' const root = resolve(import.meta.dirname, '../fixtures/browser-custom-html') test('throws an error with non-existing path', async () => { - const { stderr, thrown } = await runVitest({ + const { stderr } = await runVitest({ root, config: './vitest.config.non-existing.ts', }, [], 'test', {}, { fails: true }) - expect(thrown).toBe(true) expect(stderr).toContain(`Tester HTML file "${resolve(root, './some-non-existing-path')}" doesn't exist.`) }) test('throws an error and exits if there is an error in the html file hook', async () => { - const { stderr, stdout, exitCode } = await runVitest({ + const { stderr, exitCode } = await runVitest({ root, config: './vitest.config.error-hook.ts', }) - expect(stderr).toContain('expected error in transformIndexHtml') - // error happens when browser is opened - expect(stdout).toContain('Browser runner started by playwright') + expect(stderr).toContain('Error: expected error in transformIndexHtml') + expect(stderr).toContain('[vite] Internal server error: expected error in transformIndexHtml') expect(exitCode).toBe(1) }) @@ -31,7 +29,6 @@ test('allows correct custom html', async () => { reporters: ['basic'], }) expect(stderr).toBe('') - expect(stdout).toContain('Browser runner started by playwright') expect(stdout).toContain('✓ browser-basic.test.ts') expect(exitCode).toBe(0) }) @@ -43,7 +40,6 @@ test('allows custom transformIndexHtml with custom html file', async () => { reporters: ['basic'], }) expect(stderr).toBe('') - expect(stdout).toContain('Browser runner started by playwright') expect(stdout).toContain('✓ browser-custom.test.ts') expect(exitCode).toBe(0) }) @@ -55,7 +51,6 @@ test('allows custom transformIndexHtml without custom html file', async () => { reporters: ['basic'], }) expect(stderr).toBe('') - expect(stdout).toContain('Browser runner started by playwright') expect(stdout).toContain('✓ browser-custom.test.ts') expect(exitCode).toBe(0) })