@rstest/browser 0.10.2 → 0.10.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.
Files changed (43) hide show
  1. package/dist/{323.js → 626.js} +2 -3
  2. package/dist/browser-container/container-static/js/{188.480c3c49b5.js → 3.d3d6c6e4cf.js} +2908 -2915
  3. package/dist/browser-container/container-static/js/3.d3d6c6e4cf.js.LICENSE.txt +1 -0
  4. package/dist/browser-container/container-static/js/{index.29fc60be33.js → index.8fb1588e4a.js} +431 -455
  5. package/dist/browser-container/container-static/js/{lib-react.5ae9b90ee5.js → lib-react.62b27a21db.js} +21 -21
  6. package/dist/browser-container/container-static/js/lib-react.62b27a21db.js.LICENSE.txt +1 -0
  7. package/dist/browser-container/index.html +1 -1
  8. package/dist/browser.js +5 -5
  9. package/dist/client/dispatchTransport.d.ts +19 -0
  10. package/dist/client/public.d.ts +3 -3
  11. package/dist/client/sourceMapSupport.d.ts +1 -1
  12. package/dist/concurrency.d.ts +3 -3
  13. package/dist/configValidation.d.ts +2 -2
  14. package/dist/dispatchCapabilities.d.ts +1 -1
  15. package/dist/hostController.d.ts +3 -3
  16. package/dist/index.d.ts +5 -5
  17. package/dist/index.js +209 -221
  18. package/dist/protocol.d.ts +45 -18
  19. package/dist/providers/index.d.ts +11 -1
  20. package/dist/rpcProtocol.d.ts +32 -18
  21. package/dist/rslib-runtime.js +9 -5
  22. package/dist/viewportPresets.d.ts +5 -0
  23. package/package.json +8 -8
  24. package/src/AGENTS.md +7 -1
  25. package/src/client/dispatchTransport.ts +63 -1
  26. package/src/client/entry.ts +25 -58
  27. package/src/client/public.ts +3 -3
  28. package/src/client/snapshot.ts +24 -22
  29. package/src/client/sourceMapSupport.ts +1 -1
  30. package/src/concurrency.ts +6 -20
  31. package/src/configValidation.ts +2 -2
  32. package/src/dispatchCapabilities.ts +102 -83
  33. package/src/env.d.ts +6 -2
  34. package/src/hostController.ts +76 -42
  35. package/src/index.ts +22 -11
  36. package/src/protocol.ts +66 -22
  37. package/src/providers/index.ts +11 -1
  38. package/src/providers/playwright/runtime.ts +104 -4
  39. package/src/rpcProtocol.ts +34 -21
  40. package/src/viewportPresets.ts +5 -0
  41. package/src/watchCliShortcuts.ts +2 -4
  42. package/dist/browser-container/container-static/js/188.480c3c49b5.js.LICENSE.txt +0 -1
  43. package/dist/browser-container/container-static/js/lib-react.5ae9b90ee5.js.LICENSE.txt +0 -1
@@ -1,7 +1,7 @@
1
- import type { DevicePreset } from '@rstest/core/browser';
2
- import type { RuntimeConfig, TestFileResult, TestInfo, TestResult } from '@rstest/core/browser-runtime';
1
+ import type { BrowserViewport } from '@rstest/core/internal/browser';
2
+ import type { RuntimeConfig, TestFileResult, TestInfo, TestResult } from '@rstest/core/internal/browser-runtime';
3
3
  import type { SnapshotUpdateState } from '@vitest/snapshot';
4
- export type { BrowserLocatorIR, BrowserRpcRequest, SnapshotRpcRequest, } from './rpcProtocol.js';
4
+ export type { BrowserLocatorIR, BrowserRpcRequest, SnapshotRpcCall, SnapshotRpcMethod, SnapshotRpcMethodArgs, SnapshotRpcRequest, } from './rpcProtocol.js';
5
5
  export { validateBrowserRpcRequest } from './rpcProtocol.js';
6
6
  export declare const DISPATCH_MESSAGE_TYPE = "__rstest_dispatch__";
7
7
  export declare const DISPATCH_RESPONSE_TYPE = "__rstest_dispatch_response__";
@@ -13,10 +13,7 @@ export declare const DISPATCH_NAMESPACE_BROWSER = "browser";
13
13
  export declare const DISPATCH_NAMESPACE_SNAPSHOT = "snapshot";
14
14
  export declare const DISPATCH_METHOD_RPC = "rpc";
15
15
  export type SerializedRuntimeConfig = RuntimeConfig;
16
- export type BrowserViewport = {
17
- width: number;
18
- height: number;
19
- } | DevicePreset;
16
+ export type { BrowserViewport };
20
17
  export type BrowserProjectRuntime = {
21
18
  name: string;
22
19
  environmentName: string;
@@ -38,6 +35,24 @@ export type TestFileInfo = {
38
35
  * - 'collect': Only collect test metadata without running
39
36
  */
40
37
  export type BrowserExecutionMode = 'run' | 'collect';
38
+ /**
39
+ * Wire shape of a `log` client message payload. The host receives this and maps
40
+ * it onto core's {@link UserConsoleLog} (notably `level` → `name`); that mapper
41
+ * (`hostController.ts` `handleLog`) annotates its result as `UserConsoleLog`, so
42
+ * the map→core direction is compiler-checked. Owning the wire shape here as one
43
+ * named type keeps the host's input type from drifting away from the producer.
44
+ */
45
+ export type BrowserLogPayload = {
46
+ level: 'log' | 'warn' | 'error' | 'info' | 'debug';
47
+ content: string;
48
+ taskId?: string;
49
+ taskName?: string;
50
+ taskParentNames?: string[];
51
+ taskType?: 'file' | 'suite' | 'case';
52
+ testPath: string;
53
+ type: 'stdout' | 'stderr';
54
+ trace?: string;
55
+ };
41
56
  export type BrowserHostConfig = {
42
57
  rootPath: string;
43
58
  projects: BrowserProjectRuntime[];
@@ -88,17 +103,7 @@ export type BrowserClientMessage = {
88
103
  payload: TestFileResult;
89
104
  } | {
90
105
  type: 'log';
91
- payload: {
92
- level: 'log' | 'warn' | 'error' | 'info' | 'debug';
93
- content: string;
94
- taskId?: string;
95
- taskName?: string;
96
- taskParentNames?: string[];
97
- taskType?: 'file' | 'suite' | 'case';
98
- testPath: string;
99
- type: 'stdout' | 'stderr';
100
- trace?: string;
101
- };
106
+ payload: BrowserLogPayload;
102
107
  } | {
103
108
  type: 'fatal';
104
109
  payload: {
@@ -120,6 +125,28 @@ export type BrowserClientMessage = {
120
125
  type: typeof DISPATCH_RPC_REQUEST_TYPE;
121
126
  payload: BrowserDispatchRequest;
122
127
  };
128
+ /**
129
+ * Lifecycle methods the runner emits via `dispatchRunnerLifecycle()` as
130
+ * dispatch-rpc-requests on the `runner` namespace (as opposed to the
131
+ * {@link BrowserClientMessage} types it `send()`s). The runner client imports
132
+ * this instead of redeclaring the list, so the emit site cannot drift from the
133
+ * host router.
134
+ */
135
+ export type RunnerLifecycleMethod = 'file-ready' | 'suite-start' | 'suite-result' | 'case-start';
136
+ /**
137
+ * {@link BrowserClientMessage} types that are forwarded to the `runner`
138
+ * namespace (by message `type`) rather than handled at the transport layer.
139
+ * `Extract` keeps this a checked subset of the message union — renaming a
140
+ * message type drops it here, surfacing as a missing handler downstream.
141
+ */
142
+ type RunnerMessageMethod = Extract<BrowserClientMessage['type'], 'file-start' | 'case-result' | 'file-complete' | 'log' | 'fatal'>;
143
+ /**
144
+ * Single source of truth for every method handled by the `runner` dispatch
145
+ * namespace. The host handler table is keyed by this union (a missing key is a
146
+ * compile error), so adding a runner method here forces a matching handler and
147
+ * cannot silently no-op at runtime.
148
+ */
149
+ export type RunnerDispatchMethod = RunnerLifecycleMethod | RunnerMessageMethod;
123
150
  /**
124
151
  * Transport-agnostic envelope used by host routing.
125
152
  * `namespace + method + args + target` describes an operation independent of
@@ -1,3 +1,4 @@
1
+ import type { BrowserProvider } from '@rstest/core/internal/browser';
1
2
  import type { BrowserRpcRequest } from '../rpcProtocol.js';
2
3
  /**
3
4
  * Browser provider contract hub.
@@ -12,8 +13,14 @@ import type { BrowserRpcRequest } from '../rpcProtocol.js';
12
13
  * - do not reference optional peer provider types from public declarations
13
14
  * - keep provider-specific behavior and config decoding inside provider implementations
14
15
  * - prefer direct passthrough to provider APIs over provider-specific translation
16
+ *
17
+ * The provider-name union is owned by `@rstest/core` (see `BROWSER_PROVIDERS`)
18
+ * because core's CLI `init` templates need it but cannot import this package
19
+ * (peer-dependency direction). `providerImplementations` below is keyed by the
20
+ * re-exported union, so a provider added in core without an implementation here
21
+ * is a compile error.
15
22
  */
16
- export type BrowserProvider = 'playwright';
23
+ export type { BrowserProvider };
17
24
  /** Minimal console shape needed by host logging bridge. */
18
25
  export type BrowserConsoleMessage = {
19
26
  text: () => string;
@@ -39,16 +46,19 @@ export type BrowserProviderPage = {
39
46
  (event: 'console', listener: (message: BrowserConsoleMessage) => void): void;
40
47
  };
41
48
  close: () => Promise<void>;
49
+ [Symbol.asyncDispose]: () => Promise<void>;
42
50
  };
43
51
  /** Minimal browser context API surface required by hostController. */
44
52
  export type BrowserProviderContext = {
45
53
  newPage: () => Promise<BrowserProviderPage>;
46
54
  on: (event: 'page', listener: (page: BrowserProviderPage) => void) => void;
47
55
  close: () => Promise<void>;
56
+ [Symbol.asyncDispose]: () => Promise<void>;
48
57
  };
49
58
  /** Minimal browser API surface required by hostController. */
50
59
  export type BrowserProviderBrowser = {
51
60
  close: () => Promise<void>;
61
+ [Symbol.asyncDispose]: () => Promise<void>;
52
62
  newContext: (options: {
53
63
  viewport: {
54
64
  width: number;
@@ -101,32 +101,46 @@ export type BrowserRpcRequest = {
101
101
  };
102
102
  export declare const validateBrowserRpcRequest: (payload: unknown) => BrowserRpcRequest;
103
103
  /**
104
- * Snapshot RPC request from runner iframe.
105
- * The container will forward these to the host via WebSocket RPC.
104
+ * Single source of truth for snapshot RPC methods and their argument shapes.
105
+ * The request union, the client sender, both host-side mappers, and every
106
+ * exhaustiveness check derive from this map — adding or renaming a method here
107
+ * forces a compile error at every site that has not been updated.
106
108
  */
107
- export type SnapshotRpcRequest = {
108
- id: string;
109
- method: 'resolveSnapshotPath';
110
- args: {
109
+ export type SnapshotRpcMethodArgs = {
110
+ resolveSnapshotPath: {
111
111
  testPath: string;
112
112
  };
113
- } | {
114
- id: string;
115
- method: 'readSnapshotFile';
116
- args: {
113
+ readSnapshotFile: {
117
114
  filepath: string;
118
115
  };
119
- } | {
120
- id: string;
121
- method: 'saveSnapshotFile';
122
- args: {
116
+ saveSnapshotFile: {
123
117
  filepath: string;
124
118
  content: string;
125
119
  };
126
- } | {
127
- id: string;
128
- method: 'removeSnapshotFile';
129
- args: {
120
+ removeSnapshotFile: {
130
121
  filepath: string;
131
122
  };
132
123
  };
124
+ export type SnapshotRpcMethod = keyof SnapshotRpcMethodArgs;
125
+ /**
126
+ * Snapshot RPC request from runner iframe.
127
+ * The container will forward these to the host via WebSocket RPC.
128
+ */
129
+ export type SnapshotRpcRequest = {
130
+ [M in SnapshotRpcMethod]: {
131
+ id: string;
132
+ method: M;
133
+ args: SnapshotRpcMethodArgs[M];
134
+ };
135
+ }[SnapshotRpcMethod];
136
+ /**
137
+ * Client-side snapshot RPC call (request without the transport `id`).
138
+ * Distributive over {@link SnapshotRpcMethod} so `method` and `args` stay paired
139
+ * at the call site — a mismatched pair fails to compile.
140
+ */
141
+ export type SnapshotRpcCall = {
142
+ [M in SnapshotRpcMethod]: {
143
+ method: M;
144
+ args: SnapshotRpcMethodArgs[M];
145
+ };
146
+ }[SnapshotRpcMethod];
@@ -20,11 +20,15 @@ __webpack_require__.m = __webpack_modules__;
20
20
  };
21
21
  })();
22
22
  (()=>{
23
- __webpack_require__.d = (exports, definition)=>{
24
- for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
25
- enumerable: true,
26
- get: definition[key]
27
- });
23
+ __webpack_require__.d = (exports, getters, values)=>{
24
+ var define = (defs, kind)=>{
25
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
26
+ enumerable: true,
27
+ [kind]: defs[key]
28
+ });
29
+ };
30
+ define(getters, "get");
31
+ define(values, "value");
28
32
  };
29
33
  })();
30
34
  (()=>{
@@ -4,6 +4,11 @@
4
4
  * IMPORTANT: Keep this list/map in sync with `DevicePreset` typing in
5
5
  * `@rstest/core` (`packages/core/src/types/config.ts`) so `defineConfig`
6
6
  * autocomplete and runtime validation stay consistent.
7
+ *
8
+ * Upstream source of truth for ids/dimensions is the Chrome DevTools emulated
9
+ * device list — mirror each device's `screen.vertical` `{ width, height }`:
10
+ * https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/models/emulation/EmulatedDevices.ts
11
+ * Last verified in sync (all 17): 2026-04-22, devtools-frontend commit abaac05e.
7
12
  */
8
13
  export declare const BROWSER_VIEWPORT_PRESET_IDS: readonly ['iPhoneSE', 'iPhoneXR', 'iPhone12Pro', 'iPhone14ProMax', 'Pixel7', 'SamsungGalaxyS8Plus', 'SamsungGalaxyS20Ultra', 'iPadMini', 'iPadAir', 'iPadPro', 'SurfacePro7', 'SurfaceDuo', 'GalaxyZFold5', 'AsusZenbookFold', 'SamsungGalaxyA51A71', 'NestHub', 'NestHubMax'];
9
14
  type BrowserViewportPresetId = (typeof BROWSER_VIEWPORT_PRESET_IDS)[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rstest/browser",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Browser mode support for Rstest testing framework.",
5
5
  "keywords": [
6
6
  "rstest",
@@ -45,25 +45,25 @@
45
45
  "open-editor": "^6.0.0",
46
46
  "pathe": "^2.0.3",
47
47
  "sirv": "^3.0.2",
48
- "ws": "^8.20.1"
48
+ "ws": "^8.21.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@rslib/core": "0.21.5",
51
+ "@rslib/core": "0.22.0",
52
52
  "@types/convert-source-map": "^2.0.3",
53
53
  "@types/picomatch": "^4.0.3",
54
54
  "@types/ws": "^8.18.1",
55
- "@typescript/native-preview": "7.0.0-dev.20260516.1",
56
- "@vitest/snapshot": "^3.2.4",
55
+ "@typescript/native-preview": "7.0.0-dev.20260608.1",
56
+ "@vitest/snapshot": "^3.2.6",
57
57
  "birpc": "^4.0.0",
58
58
  "picomatch": "^4.0.4",
59
59
  "playwright": "^1.60.0",
60
+ "@rstest/core": "0.10.4",
60
61
  "@rstest/browser-ui": "0.0.0",
61
- "@rstest/tsconfig": "0.0.1",
62
- "@rstest/core": "0.10.2"
62
+ "@rstest/tsconfig": "0.0.1"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "playwright": "^1.49.1",
66
- "@rstest/core": "^0.10.2"
66
+ "@rstest/core": "^0.10.4"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "playwright": {
package/src/AGENTS.md CHANGED
@@ -15,6 +15,9 @@ flowchart LR
15
15
  SR["sessionRegistry.ts\nRunnerSessionRegistry"]
16
16
  WP["watchRerunPlanner.ts"]
17
17
  LS["headlessLatestRerunScheduler.ts"]
18
+ HT["headlessTransport.ts\nattachHeadlessRunnerTransport()"]
19
+ CC["concurrency.ts\ngetHeadlessConcurrency()"]
20
+ HQ["headedSerialTaskQueue.ts\ncreateHeadedSerialTaskQueue()"]
18
21
  end
19
22
 
20
23
  subgraph UI["@rstest/browser-ui container (headed path)"]
@@ -35,6 +38,9 @@ flowchart LR
35
38
  WP --> LS
36
39
  HC --> RL
37
40
  RL --> SR
41
+ HC --> CC
42
+ HC --> HQ
43
+ HC --> HT
38
44
 
39
45
  UR <--> HC
40
46
  MSG --> MH
@@ -42,7 +48,7 @@ flowchart LR
42
48
  RPC --> CH
43
49
  CH --> UR
44
50
 
45
- HC -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
51
+ HT -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
46
52
  ```
47
53
 
48
54
  ## Headed transport path
@@ -4,10 +4,15 @@ import type {
4
4
  } from '../protocol';
5
5
  import {
6
6
  DISPATCH_MESSAGE_TYPE,
7
+ DISPATCH_NAMESPACE_RUNNER,
7
8
  DISPATCH_RESPONSE_TYPE,
9
+ DISPATCH_RPC_BRIDGE_NAME,
8
10
  DISPATCH_RPC_REQUEST_TYPE,
9
11
  } from '../protocol';
10
12
 
13
+ // Coincidentally equal to the host-side RUNNER_FRAMES_READY_TIMEOUT_MS and the
14
+ // runner's CONFIG_WAIT_TIMEOUT_MS (entry.ts), but a semantically distinct
15
+ // default in a different runtime, so deliberately not shared with them.
11
16
  const DEFAULT_RPC_TIMEOUT_MS = 30_000;
12
17
 
13
18
  export const getRpcTimeout = (): number => {
@@ -37,6 +42,63 @@ export const createRequestId = (prefix: string): string => {
37
42
  return `${prefix}-${Date.now().toString(36)}-${requestIdCounter.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
38
43
  };
39
44
 
45
+ /**
46
+ * Build a runner-lifecycle dispatch request.
47
+ *
48
+ * Lifecycle events (`file-ready`, `suite-start`, `suite-result`, `case-start`)
49
+ * share the dispatch-rpc envelope but are delivered fire-and-forget via
50
+ * {@link sendRunnerLifecycle}, so the request id only needs to be unique — it is
51
+ * produced by the shared {@link createRequestId} factory rather than a bespoke
52
+ * per-runner counter.
53
+ */
54
+ export const createRunnerLifecycleRequest = (
55
+ method: string,
56
+ args: unknown,
57
+ ): BrowserDispatchRequest => ({
58
+ requestId: createRequestId('runner-lifecycle'),
59
+ namespace: DISPATCH_NAMESPACE_RUNNER,
60
+ method,
61
+ args,
62
+ });
63
+
64
+ /**
65
+ * Deliver a runner-lifecycle request fire-and-forget.
66
+ *
67
+ * Unlike {@link dispatchRpc}, this never awaits, unwraps, id-matches, or times
68
+ * out: the host echoes a response but the runner ignores it. Failures surface
69
+ * only through the optional `onError` hook (debug logging at the call site),
70
+ * keeping the hot test loop non-blocking.
71
+ */
72
+ export const sendRunnerLifecycle = (
73
+ request: BrowserDispatchRequest,
74
+ onError?: (error: unknown) => void,
75
+ ): void => {
76
+ if (window.parent === window) {
77
+ const dispatchBridge = window[DISPATCH_RPC_BRIDGE_NAME];
78
+ if (!dispatchBridge) {
79
+ onError?.(
80
+ new Error('Dispatch RPC bridge is not available in top-level runner.'),
81
+ );
82
+ return;
83
+ }
84
+ void Promise.resolve(dispatchBridge(request)).catch((error: unknown) => {
85
+ onError?.(error);
86
+ });
87
+ return;
88
+ }
89
+
90
+ window.parent.postMessage(
91
+ {
92
+ type: DISPATCH_MESSAGE_TYPE,
93
+ payload: {
94
+ type: DISPATCH_RPC_REQUEST_TYPE,
95
+ payload: request,
96
+ },
97
+ },
98
+ '*',
99
+ );
100
+ };
101
+
40
102
  const isDispatchResponse = (
41
103
  value: unknown,
42
104
  ): value is BrowserDispatchResponse => {
@@ -116,7 +178,7 @@ export const dispatchRpc = <T>({
116
178
  staleMessage: string;
117
179
  }): Promise<T> => {
118
180
  if (window.parent === window) {
119
- const dispatchBridge = window.__rstest_dispatch_rpc__;
181
+ const dispatchBridge = window[DISPATCH_RPC_BRIDGE_NAME];
120
182
  if (!dispatchBridge) {
121
183
  throw new Error(
122
184
  'Dispatch RPC bridge is not available in top-level runner.',
@@ -12,25 +12,26 @@ import type {
12
12
  RunnerHooks,
13
13
  RuntimeConfig,
14
14
  WorkerState,
15
- } from '@rstest/core/browser-runtime';
15
+ } from '@rstest/core/internal/browser-runtime';
16
16
  import {
17
17
  createBrowserTaskContext,
18
18
  createRstestRuntime,
19
19
  globalApis,
20
+ RSTEST_ENV_SYMBOL_KEY,
20
21
  setRealTimers,
21
- } from '@rstest/core/browser-runtime';
22
+ unwrapRegex,
23
+ } from '@rstest/core/internal/browser-runtime';
22
24
  import { normalize } from 'pathe';
23
25
  import type {
24
26
  BrowserClientMessage,
25
- BrowserDispatchRequest,
26
27
  BrowserProjectRuntime,
28
+ RunnerLifecycleMethod,
27
29
  } from '../protocol';
30
+ import { DISPATCH_MESSAGE_TYPE, RSTEST_CONFIG_MESSAGE_TYPE } from '../protocol';
28
31
  import {
29
- DISPATCH_MESSAGE_TYPE,
30
- DISPATCH_NAMESPACE_RUNNER,
31
- DISPATCH_RPC_REQUEST_TYPE,
32
- RSTEST_CONFIG_MESSAGE_TYPE,
33
- } from '../protocol';
32
+ createRunnerLifecycleRequest,
33
+ sendRunnerLifecycle,
34
+ } from './dispatchTransport';
34
35
  import { BrowserSnapshotEnvironment } from './snapshot';
35
36
  import {
36
37
  findNewScriptUrl,
@@ -44,14 +45,6 @@ declare global {
44
45
  var __coverage__: Record<string, unknown> | undefined;
45
46
  }
46
47
 
47
- type RunnerLifecycleMethod =
48
- | 'file-ready'
49
- | 'suite-start'
50
- | 'suite-result'
51
- | 'case-start';
52
-
53
- let runnerDispatchRequestId = 0;
54
-
55
48
  /**
56
49
  * Debug logger for browser client.
57
50
  * Only logs when debug mode is enabled (DEBUG=rstest on server side).
@@ -63,27 +56,13 @@ const debugLog = (...args: unknown[]): void => {
63
56
  };
64
57
 
65
58
  type RuntimeEnvStore = Record<string, string | undefined>;
66
- const RSTEST_ENV_SYMBOL = Symbol.for('rstest.env');
59
+ const RSTEST_ENV_SYMBOL = Symbol.for(RSTEST_ENV_SYMBOL_KEY);
67
60
 
68
61
  type GlobalWithRuntimeEnv = typeof globalThis &
69
62
  Record<symbol, unknown> & {
70
63
  global?: typeof globalThis;
71
64
  };
72
65
 
73
- const REGEXP_FLAG_PREFIX = 'RSTEST_REGEXP:';
74
-
75
- const unwrapRegex = (value: string): string | RegExp => {
76
- if (value.startsWith(REGEXP_FLAG_PREFIX)) {
77
- const raw = value.slice(REGEXP_FLAG_PREFIX.length);
78
- const match = raw.match(/^\/(.+)\/([gimuy]*)$/);
79
- if (match) {
80
- const [, pattern, flags] = match;
81
- return new RegExp(pattern!, flags);
82
- }
83
- }
84
- return value;
85
- };
86
-
87
66
  const restoreRuntimeConfig = (
88
67
  config: BrowserProjectRuntime['runtimeConfig'],
89
68
  ): RuntimeConfig => {
@@ -230,42 +209,30 @@ const send = (message: BrowserClientMessage): void => {
230
209
  }
231
210
  // Fallback: direct call if running outside iframe (not typical)
232
211
  // Note: This binding may not exist if not using Playwright
233
- window.__rstest_dispatch__?.(message);
212
+ window[DISPATCH_MESSAGE_TYPE]?.(message);
234
213
  };
235
214
 
236
215
  const dispatchRunnerLifecycle = (
237
216
  method: RunnerLifecycleMethod,
238
217
  payload: unknown,
239
218
  ): void => {
240
- const request: BrowserDispatchRequest = {
241
- requestId: `runner-lifecycle-${++runnerDispatchRequestId}`,
242
- namespace: DISPATCH_NAMESPACE_RUNNER,
243
- method,
244
- args: payload,
245
- };
246
-
247
- if (window.parent === window) {
248
- const dispatchBridge = window.__rstest_dispatch_rpc__;
249
- if (!dispatchBridge) {
250
- debugLog(
251
- '[Runner] Missing dispatch bridge for lifecycle method:',
252
- method,
253
- );
254
- return;
255
- }
256
- void Promise.resolve(dispatchBridge(request)).catch((error: unknown) => {
219
+ sendRunnerLifecycle(
220
+ createRunnerLifecycleRequest(method, payload),
221
+ (error: unknown) => {
257
222
  debugLog('[Runner] Failed to dispatch lifecycle method:', method, error);
258
- });
259
- return;
260
- }
261
-
262
- send({
263
- type: DISPATCH_RPC_REQUEST_TYPE,
264
- payload: request,
265
- });
223
+ },
224
+ );
266
225
  };
267
226
 
268
- /** Timeout for waiting for browser config from container (30 seconds) */
227
+ /**
228
+ * Timeout for waiting for browser config from container (30 seconds).
229
+ *
230
+ * Coincidentally equal to the RPC default (client/dispatchTransport.ts) and the
231
+ * host's RUNNER_FRAMES_READY_TIMEOUT_MS (hostController.ts), but semantically
232
+ * distinct and in a different runtime, so deliberately not shared. Implicit
233
+ * invariant: this must not exceed the host's frames-ready timeout, or the host
234
+ * declares the runner un-ready before it can even receive its config.
235
+ */
269
236
  const CONFIG_WAIT_TIMEOUT_MS = 30_000;
270
237
 
271
238
  /**
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Re-export runtime API from @rstest/core/browser-runtime for browser use.
2
+ * Re-export runtime API from @rstest/core/internal/browser-runtime for browser use.
3
3
  * This file is used as an alias target for '@rstest/core' in browser mode.
4
4
  *
5
- * Uses @rstest/core/browser-runtime which only exports the test APIs
5
+ * Uses @rstest/core/internal/browser-runtime which only exports the test APIs
6
6
  * (describe, it, expect, etc.) without any Node.js dependencies.
7
7
  */
8
8
 
9
9
  // Re-export types from @rstest/core (these are compile-time only)
10
10
  export type { Assertion, Mock } from '@rstest/core';
11
11
  // Re-export all public test APIs
12
- export * from '@rstest/core/browser-runtime';
12
+ export * from '@rstest/core/internal/browser-runtime';
@@ -1,4 +1,4 @@
1
- import type { BrowserDispatchRequest, SnapshotRpcRequest } from '../protocol';
1
+ import type { BrowserDispatchRequest, SnapshotRpcCall } from '../protocol';
2
2
  import { DISPATCH_NAMESPACE_SNAPSHOT } from '../protocol';
3
3
  import {
4
4
  createRequestId,
@@ -11,41 +11,36 @@ const SNAPSHOT_HEADER = '// Rstest Snapshot';
11
11
 
12
12
  const createSnapshotDispatchRequest = (
13
13
  requestId: string,
14
- method: SnapshotRpcRequest['method'],
15
- args: SnapshotRpcRequest['args'],
14
+ call: SnapshotRpcCall,
16
15
  ): BrowserDispatchRequest => {
17
16
  // Snapshot is just one namespace on the shared dispatch RPC channel.
18
17
  // Keep this mapping explicit so new runner-side RPC clients can mirror it.
19
18
  return {
20
19
  requestId,
21
20
  namespace: DISPATCH_NAMESPACE_SNAPSHOT,
22
- method,
23
- args,
21
+ method: call.method,
22
+ args: call.args,
24
23
  };
25
24
  };
26
25
 
27
26
  /**
28
27
  * Send a snapshot RPC request to the container (parent window).
29
28
  * The container will forward it to the host via WebSocket RPC.
29
+ *
30
+ * `call` is a {@link SnapshotRpcCall}, so `method` and `args` are validated as a
31
+ * pair — a mismatched argument shape fails to compile at the call site.
30
32
  */
31
- const sendRpcRequest = <T>(
32
- method: SnapshotRpcRequest['method'],
33
- args: SnapshotRpcRequest['args'],
34
- ): Promise<T> => {
33
+ const sendRpcRequest = <T>(call: SnapshotRpcCall): Promise<T> => {
35
34
  const requestId = createRequestId('snapshot-rpc');
36
35
  const rpcTimeout = getRpcTimeout();
37
- const dispatchRequest = createSnapshotDispatchRequest(
38
- requestId,
39
- method,
40
- args,
41
- );
36
+ const dispatchRequest = createSnapshotDispatchRequest(requestId, call);
42
37
 
43
38
  return dispatchRpc<T>({
44
39
  requestId,
45
40
  request: dispatchRequest,
46
41
  timeoutMs: rpcTimeout,
47
42
  staleMessage: 'Stale snapshot RPC request ignored.',
48
- timeoutMessage: `Snapshot RPC timeout after ${rpcTimeout / 1000}s: ${method}`,
43
+ timeoutMessage: `Snapshot RPC timeout after ${rpcTimeout / 1000}s: ${call.method}`,
49
44
  });
50
45
  };
51
46
 
@@ -67,8 +62,9 @@ export class BrowserSnapshotEnvironment {
67
62
  }
68
63
 
69
64
  async resolvePath(filepath: string): Promise<string> {
70
- return sendRpcRequest<string>('resolveSnapshotPath', {
71
- testPath: filepath,
65
+ return sendRpcRequest<string>({
66
+ method: 'resolveSnapshotPath',
67
+ args: { testPath: filepath },
72
68
  });
73
69
  }
74
70
 
@@ -77,18 +73,24 @@ export class BrowserSnapshotEnvironment {
77
73
  }
78
74
 
79
75
  async saveSnapshotFile(filepath: string, snapshot: string): Promise<void> {
80
- await sendRpcRequest<void>('saveSnapshotFile', {
81
- filepath,
82
- content: snapshot,
76
+ await sendRpcRequest<void>({
77
+ method: 'saveSnapshotFile',
78
+ args: { filepath, content: snapshot },
83
79
  });
84
80
  }
85
81
 
86
82
  async readSnapshotFile(filepath: string): Promise<string | null> {
87
- return sendRpcRequest<string | null>('readSnapshotFile', { filepath });
83
+ return sendRpcRequest<string | null>({
84
+ method: 'readSnapshotFile',
85
+ args: { filepath },
86
+ });
88
87
  }
89
88
 
90
89
  async removeSnapshotFile(filepath: string): Promise<void> {
91
- await sendRpcRequest<void>('removeSnapshotFile', { filepath });
90
+ await sendRpcRequest<void>({
91
+ method: 'removeSnapshotFile',
92
+ args: { filepath },
93
+ });
92
94
  }
93
95
 
94
96
  /**
@@ -102,7 +102,7 @@ export const preloadTestFileSourceMap = async (
102
102
  * Preload source map for the runner.js file.
103
103
  *
104
104
  * This is essential for inline snapshot support because the snapshot code
105
- * runs in runner.js (which contains @rstest/core/browser-runtime).
105
+ * runs in runner.js (which contains @rstest/core/internal/browser-runtime).
106
106
  * Without this, stack traces from inline snapshots cannot be mapped back
107
107
  * to the original source files.
108
108
  */