diff --git a/.gitignore b/.gitignore index ffa39038b56a..a40e76edc2af 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ docs/.vitepress/cache/ !test/cli/fixtures/dotted-files/**/.cache test/**/__screenshots__/**/* test/browser/fixtures/update-snapshot/basic.test.ts +test/cli/fixtures/browser-multiple/basic-* .vitest-reports diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts index a96da95ce8d1..9b0957f3ba21 100644 --- a/packages/browser/src/node/plugin.ts +++ b/packages/browser/src/node/plugin.ts @@ -8,7 +8,6 @@ import type { WorkspaceProject } from 'vitest/node' import { getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir as vitestDist } from 'vitest/node' import { type Plugin, coverageConfigDefaults } from 'vitest/config' import { toArray } from '@vitest/utils' -import { defaultBrowserPort } from 'vitest/config' import { dynamicImportPlugin } from '@vitest/mocker/node' import MagicString from 'magic-string' import BrowserContext from './plugins/pluginContext' @@ -328,14 +327,16 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => { viteConfig.esbuild.legalComments = 'inline' } + const defaultPort = project.ctx._browserLastPort++ + const api = resolveApiServerConfig( viteConfig.test?.browser || {}, - defaultBrowserPort, + defaultPort, ) viteConfig.server = { ...viteConfig.server, - port: defaultBrowserPort, + port: defaultPort, ...api, middlewareMode: false, open: false, diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index efa2f74db554..7d28c276c6ff 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -12,7 +12,7 @@ import { noop, slash, toArray } from '@vitest/utils' import { getTasks, hasFailed } from '@vitest/runner/utils' import { version } from '../../package.json' with { type: 'json' } import { getCoverageProvider } from '../integrations/coverage' -import { workspacesFiles as workspaceFiles } from '../constants' +import { defaultBrowserPort, workspacesFiles as workspaceFiles } from '../constants' import { WebSocketReporter } from '../api/setup' import type { SerializedCoverageConfig } from '../runtime/config' import type { ArgumentsType, OnServerRestartHandler, ProvidedContext, UserConsoleLog } from '../types/general' @@ -87,6 +87,9 @@ export class Vitest { /** @deprecated use `_cachedSpecs` */ projectTestFiles = this._cachedSpecs + /** @private */ + public _browserLastPort = defaultBrowserPort + constructor( public readonly mode: VitestRunMode, options: VitestOptions = {}, @@ -104,6 +107,7 @@ export class Vitest { this.unregisterWatcher?.() clearTimeout(this._rerunTimer) this.restartsCount += 1 + this._browserLastPort = defaultBrowserPort this.pool?.close?.() this.pool = undefined this.coverageProvider = undefined diff --git a/test/cli/fixtures/browser-multiple/package.json b/test/cli/fixtures/browser-multiple/package.json new file mode 100644 index 000000000000..7bb479595b21 --- /dev/null +++ b/test/cli/fixtures/browser-multiple/package.json @@ -0,0 +1,3 @@ +{ + "name": "browser-multiple" +} \ No newline at end of file diff --git a/test/cli/fixtures/browser-multiple/vitest.workspace.ts b/test/cli/fixtures/browser-multiple/vitest.workspace.ts new file mode 100644 index 000000000000..4bd2b203b471 --- /dev/null +++ b/test/cli/fixtures/browser-multiple/vitest.workspace.ts @@ -0,0 +1,31 @@ +import { resolve } from 'pathe'; +import { defineWorkspace } from 'vitest/config'; + +export default defineWorkspace([ + { + cacheDir: resolve(import.meta.dirname, 'basic-1'), + test: { + name: 'basic-1', + include: ['none'], + browser: { + enabled: true, + name: 'chromium', + provider: 'playwright', + headless: true, + } + } + }, + { + cacheDir: resolve(import.meta.dirname, 'basic-2'), + test: { + name: 'basic-2', + include: ['none'], + browser: { + enabled: true, + name: 'chromium', + provider: 'playwright', + headless: true, + } + } + }, +]) \ No newline at end of file diff --git a/test/cli/test/browser-multiple.test.ts b/test/cli/test/browser-multiple.test.ts new file mode 100644 index 000000000000..a26ca1263253 --- /dev/null +++ b/test/cli/test/browser-multiple.test.ts @@ -0,0 +1,22 @@ +import { resolve } from 'pathe' +import { expect, it, onTestFinished, vi } from 'vitest' + +import { runVitest } from '../../test-utils' + +it('automatically assigns the port', async () => { + const root = resolve(import.meta.dirname, '../fixtures/browser-multiple') + 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({ + root, + workspace, + dir: root, + watch: false, + }) + + 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/') +}) diff --git a/test/cli/vitest.config.ts b/test/cli/vitest.config.ts index beae1bdd76f5..b162a0fe9f0f 100644 --- a/test/cli/vitest.config.ts +++ b/test/cli/vitest.config.ts @@ -14,4 +14,11 @@ export default defineConfig({ truncateThreshold: 999, }, }, + server: { + watch: { + ignored: [ + '**/fixtures/browser-multiple/**/*', + ], + }, + }, }) diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts index 5650e1d8877f..93c407a6cdf9 100644 --- a/test/test-utils/index.ts +++ b/test/test-utils/index.ts @@ -2,8 +2,8 @@ import { Readable, Writable } from 'node:stream' import fs from 'node:fs' import { fileURLToPath } from 'node:url' import type { UserConfig as ViteUserConfig } from 'vite' -import { type UserConfig, type VitestRunMode, type WorkerGlobalState, afterEach, onTestFinished } from 'vitest' -import type { Vitest } from 'vitest/node' +import { type WorkerGlobalState, afterEach, onTestFinished } from 'vitest' +import type { UserConfig, Vitest, VitestRunMode } from 'vitest/node' import { startVitest } from 'vitest/node' import type { Options } from 'tinyexec' import { x } from 'tinyexec'