Another design question. Something not working currently and so we cannot tell what's designed behavior, so putting the analysis here for the record. The PR #10522 plan to remove it.
Current Behavior
The clearest way to make b do observable work is a mixed node/browser workspace where the browser project exists but no browser tests have run yet.
Example:
pnpm -C test/ui test-fixtures --root fixtures/main console.test.ts --ui
This starts UI in watch mode with node project only initially, ending with
Test Files 1 passed (1)
Tests 4 passed (4)
Start at 11:59:23
Duration 299ms (transform 18ms, setup 0ms, import 66ms, tests 10ms, environment 138ms)
PASS Waiting for file changes...
press h to show help, press q to quit
Then press b in the Vitest watch terminal will print following:
browser (chromium) Browser runner started by undefined at http://localhost:63315/__vitest_test__/
Observed behavior:
- Vitest can initialize/spawn browser server state that was previously idle.
- Vitest prints a browser runner banner for the browser project.
- The printed URL is a bare
/__vitest_test__/ URL, not a ?sessionId=... URL.
- Since the browser provider has not been initialized, the banner is also not backed by a provider-opened browser page.
- Opening that URL is not a real browser runner session under the session-bound browser model.
Why this setup matters:
fixtures/main has both a node project and a browser project.
- The filter selects only a node test.
- The browser project exists in
ctx.projects, but browser tests have not gone through the browser pool.
- Pressing
b can therefore initialize/spawn browser server state that was previously idle.
However, this is only partial initialization:
_initBrowserServers() calls _initBrowserServer() for each project.
_initBrowserServer() can create the parent browser server and spawn browser project state.
- It does not call
_initBrowserProvider().
- It does not call
_openBrowserPage().
- It does not call
BrowserSessions.createSession().
- It does not create or print
?sessionId=....
The contrast with standalone mode makes this clearer:
pnpm -C test/ui test-fixtures --root fixtures/main --standalone --ui
In this mode, browser projects already go through the full session-opening path: _initBrowserProvider(), _openBrowserPage(sessionId, ...), and _browserReadySessions.add(sessionId). Pressing b after standalone startup is effectively a no-op apart from printing the old banner, because _initBrowserServer() has already run and is deduped. It does not create another browser session or open another provider page, but simply output (and printed URL isn't functional).
browser (chromium) Browser runner started by playwright at http://localhost:63315/__vitest_test__/
Context
Vitest watch mode currently has a b shortcut on main:
packages/vitest/src/node/stdin.ts:154
if (name === 'b') {
await ctx._initBrowserServers()
ctx.projects.forEach((project) => {
ctx.logger.log()
ctx.logger.printBrowserBanner(project)
})
return null
}
This shortcut initializes browser server state and prints a browser runner banner. It does not initialize the browser provider, does not create a browser provider page, does not create a browser session, and does not print a session-bound URL.
That behavior is now questionable because browser orchestrator access is session-bound. A bare /__vitest_test__/ URL is not a valid browser runner capability and should not be advertised as one.
The normal browser execution path is different. The browser pool calls _initBrowserProvider():
packages/vitest/src/node/pools/browser.ts:91
await project._initBrowserProvider()
and _initBrowserProvider() initializes the browser server if needed, then initializes the provider:
packages/vitest/src/node/project.ts:696
_initBrowserProvider = deduped(async (): Promise<void> => {
if (!this.isBrowserEnabled() || this.browser?.provider) {
return
}
if (!this.browser) {
await this._initBrowserServer()
}
await this.browser?.initBrowserProvider(this)
})
Then browser execution opens a real session-bound page through _openBrowserPage():
packages/vitest/src/node/project.ts:635
const url = new URL('/__vitest_test__/', origin)
url.searchParams.set('sessionId', sessionId)
const otelCarrier = this.vitest._traces.getContextCarrier()
this.vitest._browserSessions.sessionIds.add(sessionId)
const sessionPromise = this.vitest._browserSessions.createSession(
sessionId,
this,
pool,
{ otelCarrier },
)
const pagePromise = this.browser.provider.openPage(
sessionId,
url.toString(),
{ parallel: pool.parallel ?? false },
)
The b shortcut stops before this session creation step, so the printed runner URL is only the old bare-orchestrator URL.
Problem
The current shortcut mixes two different concepts:
- Initializing internal browser server infrastructure.
- Giving the user a usable browser runner page.
Only the first actually happens. The second is implied by the banner, but the printed bare URL is not a valid session-bound browser runner. This is especially misleading in a session-hardened browser mode where /__vitest_test__/ without sessionId should be rejected.
Decision Needed
We should decide whether b is still a feature worth designing, or whether it should be removed as stale behavior.
There are two reasonable directions:
- Remove
b.
- Redesign
b as a real browser-session shortcut.
Option 1: Remove b
This is the smallest and most consistent with browser orchestrator hardening.
Rationale:
- The existing shortcut only performs partial initialization.
- It prints a bare URL that should no longer work.
- In normal browser test execution, browser server/provider initialization already happens through the browser pool.
- In the mixed-workspace idle-browser case, initializing only the server is not enough to give the user a useful browser runner.
- Keeping the shortcut would preserve a workflow tied to the pre-session-bound orchestrator model.
Required changes:
- Remove the
b key from watch help.
- Remove the
b handler from stdin shortcuts.
- Remove banner functionality that only exists to print bare browser runner URLs, if no remaining caller needs it.
Expected behavior:
- Watch mode no longer advertises an incomplete browser initialization shortcut.
- Users rely on actual browser test runs or standalone browser mode to open session-bound runner pages.
Option 2: Redesign b As A Real Browser-Session Shortcut
This may be useful, but it is a product/design question, not a small URL fix.
Possible desired behavior:
- In watch mode, pressing
b opens or prints a real browser runner page.
- The URL includes
?sessionId=....
- The shortcut initializes the provider, creates a browser session, and opens the provider page.
- The session is tracked like any other browser session.
- A later browser test run can either reuse the ready session or create new sessions according to the browser pool rules.
Sketch of the kind of work required:
await project._initBrowserProvider()
const sessionId = crypto.randomUUID()
await project._openBrowserPage(sessionId, {
reject: error => ctx.state.catchError(error, 'Browser Error'),
})
project._browserReadySessions.add(sessionId)
Open design questions:
- Should
b open a browser page automatically, or only print a session-bound URL?
- Which browser project should it target in a multi-project workspace?
- Which browser instance should it target when multiple instances are configured?
- Should it create one session per browser project, one per selected project, or only one interactive session?
- Should the created session run no tests, show current UI state, or prepare an idle runner for the next browser run?
- Should the next browser test run reuse this session?
- How should idle sessions be cleaned up if no browser tests run?
- Is this distinct from
--standalone, or should it be treated as an interactive watch-mode entry point into standalone browser mode?
Expected behavior:
- If designed properly,
b becomes a real “open browser runner” shortcut.
- It no longer exposes bare orchestrator access.
- It has explicit lifecycle rules for the idle session it creates.
Suggested Direction
Prefer Option 1 unless there is a clear user requirement for an interactive browser-open shortcut in watch mode.
The current implementation is not a degraded version of the desired behavior; it stops before provider/session/page creation and advertises a URL that should be invalid under session hardening. If maintainers want this workflow, it should be designed from the browser session lifecycle upward.
Related Hardening PR
This came up while reviewing fix(browser)!: require sessionId for orchestrator html request:
#10522
That PR can reasonably remove b because the existing shortcut prints the bare orchestrator URL that the PR intentionally rejects. A future issue can separately decide whether to add a properly designed session-opening shortcut.
Another design question. Something not working currently and so we cannot tell what's designed behavior, so putting the analysis here for the record. The PR #10522 plan to remove it.
Current Behavior
The clearest way to make
bdo observable work is a mixed node/browser workspace where the browser project exists but no browser tests have run yet.Example:
This starts UI in watch mode with node project only initially, ending with
Then press
bin the Vitest watch terminal will print following:Observed behavior:
/__vitest_test__/URL, not a?sessionId=...URL.Why this setup matters:
fixtures/mainhas both anodeproject and abrowserproject.ctx.projects, but browser tests have not gone through the browser pool.bcan therefore initialize/spawn browser server state that was previously idle.However, this is only partial initialization:
_initBrowserServers()calls_initBrowserServer()for each project._initBrowserServer()can create the parent browser server and spawn browser project state._initBrowserProvider()._openBrowserPage().BrowserSessions.createSession().?sessionId=....The contrast with standalone mode makes this clearer:
In this mode, browser projects already go through the full session-opening path:
_initBrowserProvider(),_openBrowserPage(sessionId, ...), and_browserReadySessions.add(sessionId). Pressingbafter standalone startup is effectively a no-op apart from printing the old banner, because_initBrowserServer()has already run and is deduped. It does not create another browser session or open another provider page, but simply output (and printed URL isn't functional).Context
Vitest watch mode currently has a
bshortcut onmain:packages/vitest/src/node/stdin.ts:154
This shortcut initializes browser server state and prints a browser runner banner. It does not initialize the browser provider, does not create a browser provider page, does not create a browser session, and does not print a session-bound URL.
That behavior is now questionable because browser orchestrator access is session-bound. A bare
/__vitest_test__/URL is not a valid browser runner capability and should not be advertised as one.The normal browser execution path is different. The browser pool calls
_initBrowserProvider():packages/vitest/src/node/pools/browser.ts:91
and
_initBrowserProvider()initializes the browser server if needed, then initializes the provider:packages/vitest/src/node/project.ts:696
Then browser execution opens a real session-bound page through
_openBrowserPage():packages/vitest/src/node/project.ts:635
The
bshortcut stops before this session creation step, so the printed runner URL is only the old bare-orchestrator URL.Problem
The current shortcut mixes two different concepts:
Only the first actually happens. The second is implied by the banner, but the printed bare URL is not a valid session-bound browser runner. This is especially misleading in a session-hardened browser mode where
/__vitest_test__/withoutsessionIdshould be rejected.Decision Needed
We should decide whether
bis still a feature worth designing, or whether it should be removed as stale behavior.There are two reasonable directions:
b.bas a real browser-session shortcut.Option 1: Remove
bThis is the smallest and most consistent with browser orchestrator hardening.
Rationale:
Required changes:
bkey from watch help.bhandler from stdin shortcuts.Expected behavior:
Option 2: Redesign
bAs A Real Browser-Session ShortcutThis may be useful, but it is a product/design question, not a small URL fix.
Possible desired behavior:
bopens or prints a real browser runner page.?sessionId=....Sketch of the kind of work required:
Open design questions:
bopen a browser page automatically, or only print a session-bound URL?--standalone, or should it be treated as an interactive watch-mode entry point into standalone browser mode?Expected behavior:
bbecomes a real “open browser runner” shortcut.Suggested Direction
Prefer Option 1 unless there is a clear user requirement for an interactive browser-open shortcut in watch mode.
The current implementation is not a degraded version of the desired behavior; it stops before provider/session/page creation and advertises a URL that should be invalid under session hardening. If maintainers want this workflow, it should be designed from the browser session lifecycle upward.
Related Hardening PR
This came up while reviewing
fix(browser)!: require sessionId for orchestrator html request:#10522
That PR can reasonably remove
bbecause the existing shortcut prints the bare orchestrator URL that the PR intentionally rejects. A future issue can separately decide whether to add a properly designed session-opening shortcut.