Skip to content

Browser-only programmatic watch(): close() does not tear down the browser runtime #1496

Description

@fi3ework

Split out from PR #1372 (programmatic JS API stabilization) — a valid Codex review finding that is out of scope for that PR (browser + programmatic watch is an edge combination; #1372 focuses on the Node programmatic API).

Problem

When instance.watch() (from @rstest/core/api) is used for a browser-only project (or when filters/shards leave only browser projects), the returned watcher's close() does not stop the browser dev server / WebSocket / browser process. An embedded host awaiting watcher.close() can hang.

Root cause

  • runTests()'s browser-only branch (packages/core/src/core/runTests.ts, the hasBrowserProjects && !hasNodeProjects block) returns void in watch mode — it never constructs an RstestWatchHandle.
  • The browser watch runtime's real teardown is cleanupWatchRuntime() (packages/browser/src/hostController.ts), which is currently only wired to process-signal handlers via registerWatchCleanup(). There is no programmatic close handle returned to the caller.
  • In watch mode closeHeadlessRuntime / closeContainerRuntime are gated behind !isWatchMode, so BrowserTestRunResult.close is undefined for watch runs — the node/mixed path's browserResult?.close?.() is a no-op there too.

Fix plan

  1. @rstest/browser hostController.ts: in watch mode, expose cleanupWatchRuntime as the result close (both the headless and container return sites).
  2. @rstest/core runTests.ts: in the browser-only branch, when in watch mode, return { close } that invokes browserResult.close() and performs trace cleanup.
  3. Consider gating registerWatchCleanup()'s process-signal install on !context.embedded, mirroring the Node watch path which skips host signal handlers in embedded mode.

Adjacent rough edge (verify while fixing)

In the browser-only watch main path, traceController.shutdown(traceRun) runs after the first run, which would close tracing before watch reruns. Confirm whether trace should stay alive across reruns.

Needs a browser-mode programmatic watch e2e to verify teardown (no leaked dev server / WS after close()).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions