diff --git a/docs/guide/browser/index.md b/docs/guide/browser/index.md index 7f7fee18d..a1073ad46 100644 --- a/docs/guide/browser/index.md +++ b/docs/guide/browser/index.md @@ -119,8 +119,6 @@ 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. - -The CLI does not print the Vite server URL automatically. 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/docs/guide/migration.md b/docs/guide/migration.md index ea3892bcb..b428acc22 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -138,6 +138,12 @@ $ cd subdir && vitest --config ../vitest.config.ts # [!code ++] Assignments to properties on `globalThis` or `window` in `jsdom` and `happy-dom` environments are now propagated to the underlying DOM implementation. Mutable properties such as `innerWidth` can affect APIs implemented by the DOM environment, for example `happy-dom`'s `matchMedia`. +### Browser Orchestrator URL Requires a Session + +Vitest no longer serves the browser orchestrator UI from a bare `/__vitest_test__/` URL. Browser runner URLs are now session-bound and must include the `sessionId` generated by Vitest, for example `/__vitest_test__/?sessionId=...`. + +If you manually opened the browser preview by copying the Vite server URL or visiting `/__vitest_test__/` directly, use the URL opened or printed by Vitest instead. + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites diff --git a/packages/browser-preview/src/preview.ts b/packages/browser-preview/src/preview.ts index 8387140df..ec22852b0 100644 --- a/packages/browser-preview/src/preview.ts +++ b/packages/browser-preview/src/preview.ts @@ -1,6 +1,5 @@ import type { SelectorOptions } from 'vitest/browser' import type { BrowserProvider, BrowserProviderOption, TestProject } from 'vitest/node' -import { nextTick } from 'node:process' import { defineBrowserProvider } from '@vitest/browser' import { resolve } from 'pathe' import { distRoot } from './constants' @@ -34,9 +33,6 @@ export class PreviewBrowserProvider implements BrowserProvider { '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', ) } - nextTick(() => { - project.vitest.logger.printBrowserBanner(project) - }) } isOpen(): boolean { @@ -49,6 +45,7 @@ export class PreviewBrowserProvider implements BrowserProvider { async openPage(_sessionId: string, url: string): Promise { this.open = true + this.project.vitest.logger.log(`Browser runner started at ${url}\n`) if (!this.project.browser) { throw new Error('Browser is not initialized') } diff --git a/packages/browser/src/node/middlewares/orchestratorMiddleware.ts b/packages/browser/src/node/middlewares/orchestratorMiddleware.ts index d9890cbe0..e98c5403a 100644 --- a/packages/browser/src/node/middlewares/orchestratorMiddleware.ts +++ b/packages/browser/src/node/middlewares/orchestratorMiddleware.ts @@ -20,6 +20,9 @@ export function createOrchestratorMiddleware(parentServer: ParentBrowserProject) res.write(html, 'utf-8') res.end() + return } + res.statusCode = 404 + res.end('Not found') } } diff --git a/packages/browser/src/node/serverOrchestrator.ts b/packages/browser/src/node/serverOrchestrator.ts index ff84adf4c..463892705 100644 --- a/packages/browser/src/node/serverOrchestrator.ts +++ b/packages/browser/src/node/serverOrchestrator.ts @@ -9,25 +9,14 @@ export async function resolveOrchestrator( url: URL, res: ServerResponse, ): Promise { - let sessionId = url.searchParams.get('sessionId') - // it's possible to open the page without a context - if (!sessionId) { - const contexts = [...globalServer.children].flatMap(p => [...p.state.orchestrators.keys()]) - sessionId = contexts.at(-1) ?? 'none' - } - - // it's ok to not have a session here, especially in the preview provider - // because the user could refresh the page which would remove the session id from the url - - const session = globalServer.vitest._browserSessions.getSession(sessionId!) - const browserProject = (session?.project.browser as ProjectBrowser | undefined) || [...globalServer.children][0] - - if (!browserProject) { + const sessionId = url.searchParams.get('sessionId') + const session = sessionId && globalServer.vitest._browserSessions.getSession(sessionId) + if (!session) { return } - // ignore unknown pages - if (sessionId && sessionId !== 'none' && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) { + const browserProject = session.project.browser as ProjectBrowser | undefined + if (!browserProject) { return } diff --git a/packages/browser/src/node/serverTester.ts b/packages/browser/src/node/serverTester.ts index a2891ed34..acfd57e06 100644 --- a/packages/browser/src/node/serverTester.ts +++ b/packages/browser/src/node/serverTester.ts @@ -22,8 +22,8 @@ export async function resolveTester( ) } - const sessionId = url.searchParams.get('sessionId') || 'none' - const session = globalServer.vitest._browserSessions.getSession(sessionId) + const sessionId = url.searchParams.get('sessionId') + const session = sessionId && globalServer.vitest._browserSessions.getSession(sessionId) if (!session) { res.statusCode = 400 diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index db4da36a3..492312c59 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -268,29 +268,6 @@ export class Logger { } } - printBrowserBanner(project: TestProject): void { - if (!project.browser) { - return - } - - const resolvedUrls = project.browser.vite.resolvedUrls - const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] - if (!origin) { - return - } - - const output = project.isRootProject() - ? '' - : formatProjectName(project) - 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('/__vitest_test__/', origin))}\n`, - ), - ) - } - printUnhandledErrors(errors: ReadonlyArray): void { const errorMessage = c.red( c.bold( diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index 1941413e5..ddfe7ae8a 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -18,7 +18,6 @@ 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()] @@ -151,14 +150,6 @@ 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/test/ui/test/browser-preview.spec.ts b/test/ui/test/browser-preview.spec.ts index e1890ee0d..9afa8aa00 100644 --- a/test/ui/test/browser-preview.spec.ts +++ b/test/ui/test/browser-preview.spec.ts @@ -5,7 +5,9 @@ import { assertTestCounts } from './helper' test.describe('orchestrator UI on preview provider', () => { test('basic', async ({ page }) => { + let previewUrl: string | undefined globalThis.__hackOpenBrowser = async (url: string) => { + previewUrl = url await page.goto(url) } const vitest = await startVitest( @@ -21,6 +23,15 @@ test.describe('orchestrator UI on preview provider', () => { }, ) + // valid `sessionId` is required for orchestrator UI + expect(new URL(previewUrl!).searchParams.get('sessionId')).toBeDefined() + const res1 = await page.request.get(new URL('/__vitest_test__/', previewUrl!).toString()) + expect(res1.status()).toBe(404) + expect(await res1.text()).toBe('Not found') + const res2 = await page.request.get(new URL('/__vitest_test__/?sessionId=invalid', previewUrl!).toString()) + expect(res2.status()).toBe(404) + expect(await res2.text()).toBe('Not found') + // results in dashboard await assertTestCounts(page, { pass: 1, fail: 0 })