@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.
- package/dist/{323.js → 626.js} +2 -3
- package/dist/browser-container/container-static/js/{188.480c3c49b5.js → 3.d3d6c6e4cf.js} +2908 -2915
- package/dist/browser-container/container-static/js/3.d3d6c6e4cf.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.29fc60be33.js → index.8fb1588e4a.js} +431 -455
- package/dist/browser-container/container-static/js/{lib-react.5ae9b90ee5.js → lib-react.62b27a21db.js} +21 -21
- package/dist/browser-container/container-static/js/lib-react.62b27a21db.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.js +5 -5
- package/dist/client/dispatchTransport.d.ts +19 -0
- package/dist/client/public.d.ts +3 -3
- package/dist/client/sourceMapSupport.d.ts +1 -1
- package/dist/concurrency.d.ts +3 -3
- package/dist/configValidation.d.ts +2 -2
- package/dist/dispatchCapabilities.d.ts +1 -1
- package/dist/hostController.d.ts +3 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +209 -221
- package/dist/protocol.d.ts +45 -18
- package/dist/providers/index.d.ts +11 -1
- package/dist/rpcProtocol.d.ts +32 -18
- package/dist/rslib-runtime.js +9 -5
- package/dist/viewportPresets.d.ts +5 -0
- package/package.json +8 -8
- package/src/AGENTS.md +7 -1
- package/src/client/dispatchTransport.ts +63 -1
- package/src/client/entry.ts +25 -58
- package/src/client/public.ts +3 -3
- package/src/client/snapshot.ts +24 -22
- package/src/client/sourceMapSupport.ts +1 -1
- package/src/concurrency.ts +6 -20
- package/src/configValidation.ts +2 -2
- package/src/dispatchCapabilities.ts +102 -83
- package/src/env.d.ts +6 -2
- package/src/hostController.ts +76 -42
- package/src/index.ts +22 -11
- package/src/protocol.ts +66 -22
- package/src/providers/index.ts +11 -1
- package/src/providers/playwright/runtime.ts +104 -4
- package/src/rpcProtocol.ts +34 -21
- package/src/viewportPresets.ts +5 -0
- package/src/watchCliShortcuts.ts +2 -4
- package/dist/browser-container/container-static/js/188.480c3c49b5.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.5ae9b90ee5.js.LICENSE.txt +0 -1
package/dist/protocol.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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;
|
package/dist/rpcProtocol.d.ts
CHANGED
|
@@ -101,32 +101,46 @@ export type BrowserRpcRequest = {
|
|
|
101
101
|
};
|
|
102
102
|
export declare const validateBrowserRpcRequest: (payload: unknown) => BrowserRpcRequest;
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
105
|
-
* The
|
|
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
|
|
108
|
-
|
|
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];
|
package/dist/rslib-runtime.js
CHANGED
|
@@ -20,11 +20,15 @@ __webpack_require__.m = __webpack_modules__;
|
|
|
20
20
|
};
|
|
21
21
|
})();
|
|
22
22
|
(()=>{
|
|
23
|
-
__webpack_require__.d = (exports,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
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.
|
|
48
|
+
"ws": "^8.21.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@rslib/core": "0.
|
|
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.
|
|
56
|
-
"@vitest/snapshot": "^3.2.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.',
|
package/src/client/entry.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
send({
|
|
263
|
-
type: DISPATCH_RPC_REQUEST_TYPE,
|
|
264
|
-
payload: request,
|
|
265
|
-
});
|
|
223
|
+
},
|
|
224
|
+
);
|
|
266
225
|
};
|
|
267
226
|
|
|
268
|
-
/**
|
|
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
|
/**
|
package/src/client/public.ts
CHANGED
|
@@ -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';
|
package/src/client/snapshot.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BrowserDispatchRequest,
|
|
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
|
-
|
|
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>(
|
|
71
|
-
|
|
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>(
|
|
81
|
-
|
|
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>(
|
|
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>(
|
|
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
|
*/
|