@rstest/browser 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- /*! LICENSE: 117.3da95caa39.js.LICENSE.txt */
@@ -1,6 +0,0 @@
1
- export declare const isBrowserWatchCliShortcutsEnabled: () => boolean;
2
- export declare const getBrowserWatchCliShortcutsHintMessage: () => string;
3
- export declare const logBrowserWatchReadyMessage: (enableCliShortcuts: boolean) => void;
4
- export declare function setupBrowserWatchCliShortcuts({ close }: {
5
- close: () => Promise<void>;
6
- }): Promise<() => void>;
package/src/AGENTS.md DELETED
@@ -1,150 +0,0 @@
1
- # Browser mode host architecture
2
-
3
- This document is architecture-only and focuses on browser mode scheduling in `@rstest/browser` host-side modules.
4
-
5
- ## Run lifecycle ownership (browser/node isomorphism)
6
-
7
- `runBrowserController` splits finalize ownership on `context.command`:
8
-
9
- - **Non-watch runs**: the host never self-finalizes. It returns a fully-populated `BrowserTestRunResult` with a deferred `close`, and `@rstest/core`'s `finalizeRunCycle` reduces the run's `ExecutorCycleOutcome`s into the verdict — reporter `onTestRunEnd`, coverage merge + report, exit code, and the bail message. `BrowserTestRunOptions.skipOnTestRunEnd` (deprecated and ignored since the finalize unification) has been removed.
10
- - **Watch runs**: the host owns the per-rerun lifecycle (`onTestRunStart`/`onTestRunEnd` per rerun) and self-finalizes; core skips its finalize entirely for browser-only and zero-node mixed watch runs.
11
-
12
- Runner lifecycle events flow through per-project `RunnerEventSink`s (`createRunnerEventSink` from core — the same event pump the node pool RPC uses). The host keeps a `Map<projectName, RunnerEventSink>` and resolves sinks via `sinkForProjectName`/`sinkForTestPath` (falling back to the first project for unknown names); it never fans out to reporters or `stateManager` directly.
13
-
14
- Cross-file `bail` is enforced at file boundaries in the headless scheduler: before picking up the next file, each worker checks the cycle-wide `stateManager.getCountOfFailedTests()` and drains the remaining queue as skipped results once the budget is reached. The headed debugging UI does not apply bail; within a running file, the runner's per-test gate uses the client-local failed count (see `src/client/AGENTS.md`).
15
-
16
- ## Module topology
17
-
18
- ```mermaid
19
- flowchart LR
20
- subgraph Host["@rstest/browser host (Node.js)"]
21
- IDX["index.ts\nrunBrowserTests()"]
22
- CV["configValidation.ts\nvalidateBrowserConfig()"]
23
- HC["hostController.ts\nrunBrowserController()"]
24
- RT["createBrowserRuntime()"]
25
- DR["dispatchCapabilities.ts + dispatchRouter.ts\nnamespace router"]
26
- BRR["browserRpcRegistry.ts\nBrowser RPC allowlists"]
27
- RL["runSession.ts\nRunSessionLifecycle"]
28
- SR["sessionRegistry.ts\nRunnerSessionRegistry"]
29
- WP["watchRerunPlanner.ts"]
30
- LS["headlessLatestRerunScheduler.ts"]
31
- HT["headlessTransport.ts\nattachHeadlessRunnerTransport()"]
32
- CC["concurrency.ts\ngetHeadlessConcurrency()"]
33
- HQ["headedSerialTaskQueue.ts\ncreateHeadedSerialTaskQueue()"]
34
- end
35
-
36
- subgraph UI["@rstest/browser-ui container (headed path)"]
37
- UR["useRpc() / birpc"]
38
- CH["core/channel.ts\nforwardDispatchRpcRequest()"]
39
- MH["main.tsx message listener\nforward lifecycle callbacks"]
40
- end
41
-
42
- subgraph Runner["runner runtime (src/client/entry.ts)"]
43
- MSG["runner lifecycle messages"]
44
- RPC["dispatch-rpc-request"]
45
- end
46
-
47
- IDX -."re-export; invoked by @rstest/core before the run".-> CV
48
- IDX --> HC
49
- HC --> RT
50
- HC --> DR
51
- DR --> BRR
52
- HC --> WP
53
- WP --> LS
54
- HC --> RL
55
- RL --> SR
56
- HC --> CC
57
- HC --> HQ
58
- HC --> HT
59
-
60
- UR <--> HC
61
- MSG --> MH
62
- MH --> UR
63
- RPC --> CH
64
- CH --> UR
65
-
66
- HT -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
67
- ```
68
-
69
- ## Headed transport path
70
-
71
- Primary dispatch request direction is `Runner -> Container -> Host -> Router -> Handler`.
72
- `Host -> Container` in this path is bootstrap setup and callback delivery, not router request initiation.
73
- `dispatchRouter` handles inbound request routing only; outbound response delivery is a transport reply.
74
-
75
- ### Bootstrap control plane
76
-
77
- ```mermaid
78
- sequenceDiagram
79
- participant Host as browser hostController
80
- participant Container as browser-ui container
81
-
82
- Host->>Container: open container and establish birpc
83
- Host->>Container: provide BrowserHostConfig
84
- Container-->>Host: getTestFiles and rerun requests
85
- ```
86
-
87
- ### Runtime dispatch RPC data plane
88
-
89
- ```mermaid
90
- sequenceDiagram
91
- participant Runner as client iframe runner
92
- participant Container as browser-ui channel
93
- participant Host as browser hostController
94
- participant Router as browser dispatchRouter
95
- participant Handler as namespace handler
96
-
97
- Runner->>Container: postMessage runner lifecycle
98
- Container->>Host: forward lifecycle callbacks (onTest*)
99
- Host->>Host: feed per-project RunnerEventSink (stateManager + reporters)
100
-
101
- Runner->>Container: postMessage dispatch rpc request
102
- Container->>Host: rpc.dispatch(request)
103
- Host->>Router: routing inbound request
104
- Router->>Handler: resolve namespace handler
105
- Handler->>Host: execute host capability work
106
- Host-->>Handler: capability result or error
107
- Handler-->>Router: return handler result
108
- Router-->>Host: routing done, response payload
109
- Host-->>Container: transport reply payload
110
- Container-->>Runner: transport reply to runner
111
- ```
112
-
113
- ## Headless transport path
114
-
115
- Primary dispatch request direction is `Runner -> Host -> Router -> Handler`.
116
- `Host -> Runner` in this path is bridge registration, not router request initiation.
117
- `dispatchRouter` handles inbound request routing only; outbound response delivery is a transport reply.
118
-
119
- ### Bootstrap control plane
120
-
121
- ```mermaid
122
- sequenceDiagram
123
- participant Host as browser hostController
124
- participant Runner as client top level runner
125
-
126
- Host->>Runner: exposeFunction __rstest_dispatch__
127
- Host->>Runner: exposeFunction __rstest_dispatch_rpc__
128
- ```
129
-
130
- ### Runtime dispatch RPC data plane
131
-
132
- ```mermaid
133
- sequenceDiagram
134
- participant Runner as client top level runner
135
- participant Host as browser hostController
136
- participant Router as browser dispatchRouter
137
- participant Handler as namespace handler
138
-
139
- Runner->>Host: __rstest_dispatch__ lifecycle
140
- Host->>Host: feed per-project RunnerEventSink (stateManager + reporters)
141
-
142
- Runner->>Host: __rstest_dispatch_rpc__ request
143
- Host->>Router: routing inbound request
144
- Router->>Handler: resolve namespace handler
145
- Handler->>Host: execute host capability work
146
- Host-->>Handler: capability result or error
147
- Handler-->>Router: return handler result
148
- Router-->>Host: routing done, response payload
149
- Host-->>Runner: transport reply payload
150
- ```
@@ -1,89 +0,0 @@
1
- # Browser mode runner architecture
2
-
3
- This document is architecture-only and focuses on the browser runner runtime in `src/client`.
4
-
5
- ## Runner bootstrap pipeline
6
-
7
- ```mermaid
8
- flowchart TD
9
- A["waitForConfig()"] --> B["read __RSTEST_BROWSER_OPTIONS__ + URL overrides"]
10
- B --> R["send ready"]
11
- R --> C["setRealTimers()"]
12
- C --> D["preloadRunnerSourceMap()"]
13
- D --> E["resolve project + runtimeConfig"]
14
- E --> F{"execution mode"}
15
-
16
- F -->|collect| G["create runtime + load setup/test modules + runner.collectTests()"]
17
- G --> H["send collect-result / collect-complete"]
18
-
19
- F -->|run| I["interceptConsole() + createRstestRuntime()"]
20
- I --> J["send file-start"]
21
- J --> K["load setup files + load test module"]
22
- K --> L["runner.runTests() + send case-result"]
23
- L --> N["send file-complete per file"]
24
- N --> O["send complete after all files"]
25
-
26
- H --> M["window.__RSTEST_DONE__ = true"]
27
- O --> M
28
- ```
29
-
30
- ## Transport architecture
31
-
32
- ```mermaid
33
- flowchart LR
34
- subgraph IframePath["Iframe path (headed)"]
35
- S1["send()"] --> P1["parent.postMessage(__rstest_dispatch__)"]
36
- R1["dispatchRunnerLifecycle()"] --> P2["postMessage dispatch-rpc-request"]
37
- SN1["snapshot.ts / browserRpc.ts via dispatchTransport.dispatchRpc()"] --> P3["postMessage dispatch-rpc-request + wait __rstest_dispatch_response__"]
38
- end
39
-
40
- subgraph TopLevelRunPath["Top-level page path (headless run)"]
41
- S2["send()"] --> D1["window.__rstest_dispatch__"]
42
- R2["dispatchRunnerLifecycle()"] --> D2["window.__rstest_dispatch_rpc__"]
43
- SN2["snapshot.ts / browserRpc.ts via dispatchTransport.dispatchRpc()"] --> D3["window.__rstest_dispatch_rpc__"]
44
- end
45
-
46
- subgraph TopLevelCollectPath["Top-level page path (list collect)"]
47
- S3["send()"] --> C1["window.__rstest_dispatch__"]
48
- end
49
- ```
50
-
51
- ## Dispatch RPC sequence (snapshot / browser namespaces)
52
-
53
- The `snapshot` namespace (snapshot file ops) and the `browser` namespace
54
- (locator/page RPC from `browserRpc.ts`) share one client channel:
55
- `dispatchTransport.ts` owns request ids, timeouts, and pending-response
56
- resolution for both the iframe and top-level transports.
57
-
58
- ```mermaid
59
- sequenceDiagram
60
- participant Caller as snapshot.ts / browserRpc.ts
61
- participant Transport as dispatchTransport.ts
62
- participant Container as browser-ui channel
63
- participant Host as host dispatch router
64
-
65
- Caller->>Transport: dispatchRpc(request)
66
-
67
- alt top-level runner (headless run)
68
- Transport->>Host: __rstest_dispatch_rpc__(namespace=snapshot|browser)
69
- Host-->>Transport: BrowserDispatchResponse
70
- Transport-->>Caller: result/error
71
- else iframe runner (headed)
72
- Transport->>Container: postMessage(dispatch-rpc-request)
73
- Container->>Host: rpc.dispatch(request)
74
- Host-->>Container: BrowserDispatchResponse
75
- Container-->>Transport: __rstest_dispatch_response__
76
- Transport-->>Caller: resolve/reject pending request
77
- end
78
- ```
79
-
80
- List collect mode does not use the snapshot or browser RPC namespaces.
81
-
82
- ## Runtime invariants
83
-
84
- - `entry.ts` is the only bootstrap entry and decides `collect` vs `run` mode.
85
- - Runner lifecycle events (`file-ready`, `suite-start`, `suite-result`, `case-start`) go through the `runner` dispatch namespace.
86
- - Snapshot file operations go through the `snapshot` dispatch namespace and never access filesystem directly in browser runtime.
87
- - Console interception is per test file and must restore original console methods in `finally`.
88
- - An unhandled window error or `unhandledrejection` that escapes a test file fails the file even when every test passed. Before finalizing each file result, the runner yields two macrotasks so a rejection leaked by a synchronous test is still observed (the browser dispatches `unhandledrejection` in a task queued after the current task).
89
- - The `getCountOfFailedTests` runner hook returns the client-local per-file failed count; cross-file `bail` is enforced host-side at file boundaries (see `../AGENTS.md`), not by this hook.
@@ -1,77 +0,0 @@
1
- /** Stringify a single console argument for terminal forwarding. */
2
- export const formatArg = (arg: unknown): string => {
3
- if (arg === null) return 'null';
4
- if (arg === undefined) return 'undefined';
5
- if (typeof arg === 'string') return arg;
6
- if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
7
- // `JSON.stringify(symbol)` is `undefined`, so a symbol must be handled before
8
- // the JSON path or it would forward as an empty/`undefined` log.
9
- if (typeof arg === 'symbol') return arg.toString();
10
- if (arg instanceof Error) {
11
- return arg.stack || `${arg.name}: ${arg.message}`;
12
- }
13
- try {
14
- return JSON.stringify(arg, null, 2);
15
- } catch {
16
- return String(arg);
17
- }
18
- };
19
-
20
- // Format specifiers understood by the browser console. `%c` applies CSS in a
21
- // real console; forwarded to a terminal there is no styling, so it is consumed
22
- // and dropped (matching how Node's `util.format` handles it). `%%` is a literal
23
- // percent sign. Kept aligned with the browser console spec (no Node-only `%j`,
24
- // since these logs originate in the browser).
25
- // cspell:ignore sdifo WHATWG
26
- const HAS_SPECIFIER = /%[sdifoOc%]/;
27
- const SPECIFIER = /%[sdifoOc%]/g;
28
-
29
- /**
30
- * Join console arguments the way a console does. When the first argument is a
31
- * string carrying `printf`-style specifiers, substitute the following arguments
32
- * into it (consuming `%c` styles without emitting them), then append any
33
- * leftover arguments. Otherwise fall back to a plain space-join.
34
- *
35
- * Without this, `console.info('%cText', 'font-weight:bold')` (e.g. React's
36
- * DevTools notice) leaks the raw `%c` directive and its CSS argument into the
37
- * forwarded terminal output.
38
- */
39
- export const formatConsoleArgs = (args: unknown[]): string => {
40
- const first = args[0];
41
- if (typeof first !== 'string' || !HAS_SPECIFIER.test(first)) {
42
- return args.map(formatArg).join(' ');
43
- }
44
-
45
- let next = 1;
46
- const substituted = first.replace(SPECIFIER, (spec) => {
47
- if (spec === '%%') return '%';
48
- if (next >= args.length) return spec;
49
- const arg = args[next++];
50
- switch (spec) {
51
- case '%c':
52
- return ''; // CSS directive: swallow the style argument
53
- case '%s':
54
- return formatArg(arg);
55
- case '%d':
56
- case '%i': {
57
- // Match the browser console formatter (WHATWG): parseInt on the value,
58
- // so unit-suffixed strings like '42px' format as 42 (not NaN).
59
- // `String()` first keeps Symbol safe — a bare `parseInt(symbol)` throws
60
- // via ToString, whereas the spec maps a Symbol to NaN.
61
- const num = Number.parseInt(String(arg), 10);
62
- return Number.isNaN(num) ? 'NaN' : String(num);
63
- }
64
- case '%f': {
65
- const num = Number.parseFloat(String(arg));
66
- return Number.isNaN(num) ? 'NaN' : String(num);
67
- }
68
- default: // %o, %O
69
- return formatArg(arg);
70
- }
71
- });
72
-
73
- if (next >= args.length) {
74
- return substituted;
75
- }
76
- return [substituted, ...args.slice(next).map(formatArg)].join(' ');
77
- };
@@ -1,12 +0,0 @@
1
- /**
2
- * Re-export runtime API from @rstest/core/internal/browser-runtime for browser use.
3
- * This file is used as an alias target for '@rstest/core' in browser mode.
4
- *
5
- * Uses @rstest/core/internal/browser-runtime which only exports the test APIs
6
- * (describe, it, expect, etc.) without any Node.js dependencies.
7
- */
8
-
9
- // Re-export types from @rstest/core (these are compile-time only)
10
- export type { Assertion, Mock } from '@rstest/core';
11
- // Re-export all public test APIs
12
- export * from '@rstest/core/internal/browser-runtime';
@@ -1,77 +0,0 @@
1
- import { color, isTTY, logger } from '@rstest/core/internal/browser';
2
-
3
- export const isBrowserWatchCliShortcutsEnabled = (): boolean => isTTY('stdin');
4
-
5
- export const getBrowserWatchCliShortcutsHintMessage = (): string => {
6
- return ` ${color.dim('press')} ${color.bold('q')} ${color.dim('to quit')}\n`;
7
- };
8
-
9
- export const logBrowserWatchReadyMessage = (
10
- enableCliShortcuts: boolean,
11
- ): void => {
12
- logger.log(color.green(' Waiting for file changes...'));
13
-
14
- if (enableCliShortcuts) {
15
- logger.log(getBrowserWatchCliShortcutsHintMessage());
16
- }
17
- };
18
-
19
- export async function setupBrowserWatchCliShortcuts({
20
- close,
21
- }: {
22
- close: () => Promise<void>;
23
- }): Promise<() => void> {
24
- const { emitKeypressEvents } = await import('node:readline');
25
-
26
- emitKeypressEvents(process.stdin);
27
- process.stdin.setRawMode(true);
28
- process.stdin.resume();
29
- process.stdin.setEncoding('utf8');
30
-
31
- let isClosing = false;
32
-
33
- const handleKeypress = (
34
- str: string,
35
- key: { name: string; ctrl: boolean },
36
- ) => {
37
- if (key.ctrl && key.name === 'c') {
38
- process.kill(process.pid, 'SIGINT');
39
- return;
40
- }
41
-
42
- if (key.ctrl && key.name === 'z') {
43
- if (process.platform !== 'win32') {
44
- process.kill(process.pid, 'SIGTSTP');
45
- }
46
- return;
47
- }
48
-
49
- if (str !== 'q' || isClosing) {
50
- return;
51
- }
52
-
53
- // TODO: Support more browser watch shortcuts only after this path is
54
- // refactored to share the same shortcut model as node mode.
55
- isClosing = true;
56
- void (async () => {
57
- try {
58
- await close();
59
- } finally {
60
- process.exit(0);
61
- }
62
- })();
63
- };
64
-
65
- process.stdin.on('keypress', handleKeypress);
66
-
67
- return () => {
68
- try {
69
- process.stdin.setRawMode(false);
70
- process.stdin.pause();
71
- } catch {
72
- // do nothing
73
- }
74
-
75
- process.stdin.off('keypress', handleKeypress);
76
- };
77
- }