@rstest/browser 0.10.3 → 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/626.js +2 -1
- package/dist/browser-container/container-static/js/{27.099feaf1da.js → 3.d3d6c6e4cf.js} +436 -436
- package/dist/browser-container/container-static/js/3.d3d6c6e4cf.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.be8c8d2626.js → index.8fb1588e4a.js} +109 -136
- 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 +4 -4
- 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 +169 -216
- package/dist/protocol.d.ts +45 -18
- package/dist/providers/index.d.ts +8 -1
- package/dist/rpcProtocol.d.ts +32 -18
- package/dist/viewportPresets.d.ts +5 -0
- package/package.json +6 -6
- 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 +8 -1
- 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/27.099feaf1da.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.5ae9b90ee5.js.LICENSE.txt +0 -1
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type { Reporter } from '@rstest/core/browser';
|
|
1
|
+
import type { Reporter } from '@rstest/core/internal/browser';
|
|
2
2
|
import { HostDispatchRouter } from './dispatchRouter';
|
|
3
|
+
import {
|
|
4
|
+
DISPATCH_NAMESPACE_RUNNER,
|
|
5
|
+
DISPATCH_NAMESPACE_SNAPSHOT,
|
|
6
|
+
} from './protocol';
|
|
3
7
|
import type {
|
|
4
8
|
BrowserClientMessage,
|
|
5
9
|
BrowserDispatchHandler,
|
|
6
10
|
BrowserDispatchRequest,
|
|
11
|
+
RunnerDispatchMethod,
|
|
12
|
+
SnapshotRpcMethod,
|
|
13
|
+
SnapshotRpcMethodArgs,
|
|
7
14
|
SnapshotRpcRequest,
|
|
8
15
|
} from './protocol';
|
|
9
16
|
|
|
@@ -52,37 +59,50 @@ type CreateHostDispatchRouterOptions = {
|
|
|
52
59
|
onDuplicateNamespace?: (namespace: string) => void;
|
|
53
60
|
};
|
|
54
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Builds a typed {@link SnapshotRpcRequest} from the untrusted wire envelope,
|
|
64
|
+
* one builder per method. Keyed by {@link SnapshotRpcMethod}, so adding or
|
|
65
|
+
* renaming a method in the union forces a matching entry here — a stale name
|
|
66
|
+
* becomes a compile error instead of silently falling through to `null`. The
|
|
67
|
+
* `args` casts are the unavoidable trust boundary for inbound wire data.
|
|
68
|
+
*/
|
|
69
|
+
const snapshotRequestBuilders: {
|
|
70
|
+
[M in SnapshotRpcMethod]: (
|
|
71
|
+
id: string,
|
|
72
|
+
args: BrowserDispatchRequest['args'],
|
|
73
|
+
) => Extract<SnapshotRpcRequest, { method: M }>;
|
|
74
|
+
} = {
|
|
75
|
+
resolveSnapshotPath: (id, args) => ({
|
|
76
|
+
id,
|
|
77
|
+
method: 'resolveSnapshotPath',
|
|
78
|
+
args: args as SnapshotRpcMethodArgs['resolveSnapshotPath'],
|
|
79
|
+
}),
|
|
80
|
+
readSnapshotFile: (id, args) => ({
|
|
81
|
+
id,
|
|
82
|
+
method: 'readSnapshotFile',
|
|
83
|
+
args: args as SnapshotRpcMethodArgs['readSnapshotFile'],
|
|
84
|
+
}),
|
|
85
|
+
saveSnapshotFile: (id, args) => ({
|
|
86
|
+
id,
|
|
87
|
+
method: 'saveSnapshotFile',
|
|
88
|
+
args: args as SnapshotRpcMethodArgs['saveSnapshotFile'],
|
|
89
|
+
}),
|
|
90
|
+
removeSnapshotFile: (id, args) => ({
|
|
91
|
+
id,
|
|
92
|
+
method: 'removeSnapshotFile',
|
|
93
|
+
args: args as SnapshotRpcMethodArgs['removeSnapshotFile'],
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
|
|
55
97
|
const toSnapshotRpcRequest = (
|
|
56
98
|
request: BrowserDispatchRequest,
|
|
57
99
|
): SnapshotRpcRequest | null => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
65
|
-
case 'readSnapshotFile':
|
|
66
|
-
return {
|
|
67
|
-
id: request.requestId,
|
|
68
|
-
method: 'readSnapshotFile',
|
|
69
|
-
args: request.args as { filepath: string },
|
|
70
|
-
};
|
|
71
|
-
case 'saveSnapshotFile':
|
|
72
|
-
return {
|
|
73
|
-
id: request.requestId,
|
|
74
|
-
method: 'saveSnapshotFile',
|
|
75
|
-
args: request.args as { filepath: string; content: string },
|
|
76
|
-
};
|
|
77
|
-
case 'removeSnapshotFile':
|
|
78
|
-
return {
|
|
79
|
-
id: request.requestId,
|
|
80
|
-
method: 'removeSnapshotFile',
|
|
81
|
-
args: request.args as { filepath: string },
|
|
82
|
-
};
|
|
83
|
-
default:
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
100
|
+
const builder = snapshotRequestBuilders[
|
|
101
|
+
request.method as SnapshotRpcMethod
|
|
102
|
+
] as
|
|
103
|
+
| ((id: string, args: BrowserDispatchRequest['args']) => SnapshotRpcRequest)
|
|
104
|
+
| undefined;
|
|
105
|
+
return builder ? builder(request.requestId, request.args) : null;
|
|
86
106
|
};
|
|
87
107
|
|
|
88
108
|
export const createHostDispatchRouter = ({
|
|
@@ -94,61 +114,60 @@ export const createHostDispatchRouter = ({
|
|
|
94
114
|
}: CreateHostDispatchRouterOptions): HostDispatchRouter => {
|
|
95
115
|
const router = new HostDispatchRouter(routerOptions);
|
|
96
116
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
break;
|
|
129
|
-
case 'file-complete':
|
|
130
|
-
await runnerCallbacks.onTestFileComplete(
|
|
131
|
-
request.args as RunnerPayload<'file-complete'>,
|
|
132
|
-
);
|
|
133
|
-
break;
|
|
134
|
-
case 'log':
|
|
135
|
-
await runnerCallbacks.onLog(request.args as RunnerPayload<'log'>);
|
|
136
|
-
break;
|
|
137
|
-
case 'fatal':
|
|
138
|
-
await runnerCallbacks.onFatal(request.args as RunnerPayload<'fatal'>);
|
|
139
|
-
break;
|
|
140
|
-
default:
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
});
|
|
117
|
+
// Keyed by RunnerDispatchMethod so adding a runner method to that union forces
|
|
118
|
+
// a handler entry here (a missing key is a compile error) — the previous
|
|
119
|
+
// `switch` with `default: break` silently dropped unhandled methods. The
|
|
120
|
+
// `args` casts are checked against each callback's parameter type, so a wrong
|
|
121
|
+
// payload type also fails to compile.
|
|
122
|
+
const runnerMethodHandlers: {
|
|
123
|
+
[M in RunnerDispatchMethod]: (
|
|
124
|
+
args: BrowserDispatchRequest['args'],
|
|
125
|
+
) => Promise<void>;
|
|
126
|
+
} = {
|
|
127
|
+
'file-start': (args) =>
|
|
128
|
+
runnerCallbacks.onTestFileStart(args as RunnerPayload<'file-start'>),
|
|
129
|
+
'file-ready': (args) =>
|
|
130
|
+
runnerCallbacks.onTestFileReady(args as RunnerDispatchFileReadyPayload),
|
|
131
|
+
'suite-start': (args) =>
|
|
132
|
+
runnerCallbacks.onTestSuiteStart(args as RunnerDispatchSuiteStartPayload),
|
|
133
|
+
'suite-result': (args) =>
|
|
134
|
+
runnerCallbacks.onTestSuiteResult(
|
|
135
|
+
args as RunnerDispatchSuiteResultPayload,
|
|
136
|
+
),
|
|
137
|
+
'case-start': (args) =>
|
|
138
|
+
runnerCallbacks.onTestCaseStart(args as RunnerDispatchCaseStartPayload),
|
|
139
|
+
'case-result': (args) =>
|
|
140
|
+
runnerCallbacks.onTestCaseResult(args as RunnerPayload<'case-result'>),
|
|
141
|
+
'file-complete': (args) =>
|
|
142
|
+
runnerCallbacks.onTestFileComplete(
|
|
143
|
+
args as RunnerPayload<'file-complete'>,
|
|
144
|
+
),
|
|
145
|
+
log: (args) => runnerCallbacks.onLog(args as RunnerPayload<'log'>),
|
|
146
|
+
fatal: (args) => runnerCallbacks.onFatal(args as RunnerPayload<'fatal'>),
|
|
147
|
+
};
|
|
144
148
|
|
|
145
|
-
router.register(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
router.register(
|
|
150
|
+
DISPATCH_NAMESPACE_RUNNER,
|
|
151
|
+
async (request: BrowserDispatchRequest) => {
|
|
152
|
+
// `request.method` is untrusted wire data, so the lookup may miss. Unknown
|
|
153
|
+
// methods are ignored for forward-compatibility with newer runners,
|
|
154
|
+
// matching the previous `default: break`.
|
|
155
|
+
await runnerMethodHandlers[request.method as RunnerDispatchMethod]?.(
|
|
156
|
+
request.args,
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
router.register(
|
|
162
|
+
DISPATCH_NAMESPACE_SNAPSHOT,
|
|
163
|
+
async (request: BrowserDispatchRequest) => {
|
|
164
|
+
const snapshotRequest = toSnapshotRpcRequest(request);
|
|
165
|
+
if (!snapshotRequest) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return runSnapshotRpc(snapshotRequest);
|
|
169
|
+
},
|
|
170
|
+
);
|
|
152
171
|
|
|
153
172
|
for (const [namespace, handler] of extensionHandlers ?? []) {
|
|
154
173
|
if (router.has(namespace)) {
|
package/src/env.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DISPATCH_MESSAGE_TYPE, DISPATCH_RPC_BRIDGE_NAME } from './protocol';
|
|
1
2
|
import type {
|
|
2
3
|
BrowserClientMessage,
|
|
3
4
|
BrowserDispatchRequest,
|
|
@@ -38,8 +39,11 @@ declare global {
|
|
|
38
39
|
|
|
39
40
|
interface Window {
|
|
40
41
|
__RSTEST_BROWSER_OPTIONS__?: BrowserHostConfig;
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
// Keyed by the sentinel constants so the declared global name and the
|
|
43
|
+
// constant are the same source — renaming a constant moves this key with it,
|
|
44
|
+
// and every `window[CONST]` access site stays in lockstep automatically.
|
|
45
|
+
[DISPATCH_MESSAGE_TYPE]?: (message: BrowserClientMessage) => void;
|
|
46
|
+
[DISPATCH_RPC_BRIDGE_NAME]?: (
|
|
43
47
|
request: BrowserDispatchRequest,
|
|
44
48
|
) => Promise<unknown>;
|
|
45
49
|
__rstest_container_dispatch__?: (data: unknown) => void;
|
package/src/hostController.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type CoverageMapData,
|
|
12
12
|
color,
|
|
13
13
|
createCoverageProvider,
|
|
14
|
+
DEFAULT_TEST_TIMEOUT,
|
|
14
15
|
type FormattedError,
|
|
15
16
|
getNoTestFilesMessage,
|
|
16
17
|
getSetupFiles,
|
|
@@ -22,16 +23,17 @@ import {
|
|
|
22
23
|
PhaseTracker,
|
|
23
24
|
type ProjectContext,
|
|
24
25
|
type Reporter,
|
|
25
|
-
type
|
|
26
|
+
type RstestContext,
|
|
26
27
|
type RuntimeConfig,
|
|
27
28
|
resolveProjectBuildCache,
|
|
29
|
+
RSTEST_ENV_SYMBOL_KEY,
|
|
28
30
|
rsbuild,
|
|
29
31
|
serializableConfig,
|
|
30
32
|
type Test,
|
|
31
33
|
type TestFileResult,
|
|
32
34
|
type TestResult,
|
|
33
35
|
type UserConsoleLog,
|
|
34
|
-
} from '@rstest/core/browser';
|
|
36
|
+
} from '@rstest/core/internal/browser';
|
|
35
37
|
import { type BirpcReturn, createBirpc } from 'birpc';
|
|
36
38
|
import openEditor from 'open-editor';
|
|
37
39
|
import { basename, dirname, join, normalize, relative, resolve } from 'pathe';
|
|
@@ -52,6 +54,7 @@ import type {
|
|
|
52
54
|
BrowserDispatchRequest,
|
|
53
55
|
BrowserDispatchResponse,
|
|
54
56
|
BrowserHostConfig,
|
|
57
|
+
BrowserLogPayload,
|
|
55
58
|
BrowserProjectRuntime,
|
|
56
59
|
BrowserRpcRequest,
|
|
57
60
|
BrowserViewport,
|
|
@@ -60,6 +63,7 @@ import type {
|
|
|
60
63
|
} from './protocol';
|
|
61
64
|
import {
|
|
62
65
|
DISPATCH_MESSAGE_TYPE,
|
|
66
|
+
DISPATCH_NAMESPACE_BROWSER,
|
|
63
67
|
DISPATCH_NAMESPACE_RUNNER,
|
|
64
68
|
validateBrowserRpcRequest,
|
|
65
69
|
} from './protocol';
|
|
@@ -97,6 +101,15 @@ type RsbuildInstance = rsbuild.RsbuildInstance;
|
|
|
97
101
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
98
102
|
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
99
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Extra time added on top of a file's `testTimeout` before the host gives up
|
|
106
|
+
* waiting for that file's RPC to settle. Covers fixed per-file overhead (page
|
|
107
|
+
* navigation, runner boot) that is not part of the user's test budget. The
|
|
108
|
+
* headless and headed scheduling paths both apply this same buffer, so it lives
|
|
109
|
+
* here as one constant to keep their per-file timeout semantics identical.
|
|
110
|
+
*/
|
|
111
|
+
const PER_FILE_TIMEOUT_BUFFER_MS = 30_000;
|
|
112
|
+
|
|
100
113
|
/**
|
|
101
114
|
* Monotonic counter for synthetic per-file Perfetto `pid` values in `--trace`
|
|
102
115
|
* mode. Browser host runs every test file inside the same Node process, so
|
|
@@ -165,18 +178,8 @@ type TestFileStartPayload = {
|
|
|
165
178
|
projectName: string;
|
|
166
179
|
};
|
|
167
180
|
|
|
168
|
-
/** Payload for log event */
|
|
169
|
-
type LogPayload =
|
|
170
|
-
level: 'log' | 'warn' | 'error' | 'info' | 'debug';
|
|
171
|
-
content: string;
|
|
172
|
-
taskId?: string;
|
|
173
|
-
taskName?: string;
|
|
174
|
-
taskParentNames?: string[];
|
|
175
|
-
taskType?: 'file' | 'suite' | 'case';
|
|
176
|
-
testPath: string;
|
|
177
|
-
type: 'stdout' | 'stderr';
|
|
178
|
-
trace?: string;
|
|
179
|
-
};
|
|
181
|
+
/** Payload for log event — single-sourced from the wire protocol. */
|
|
182
|
+
type LogPayload = BrowserLogPayload;
|
|
180
183
|
|
|
181
184
|
/** Payload for fatal error event */
|
|
182
185
|
type FatalPayload = {
|
|
@@ -830,11 +833,16 @@ const getRuntimeConfigFromProject = (
|
|
|
830
833
|
} = project.normalizedConfig;
|
|
831
834
|
|
|
832
835
|
return {
|
|
833
|
-
// Propagate NODE_ENV from the host so
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
+
// Propagate NODE_ENV and the RSTEST flag from the host so
|
|
837
|
+
// `process.env.NODE_ENV` / `process.env.RSTEST` (rewritten to the
|
|
838
|
+
// `RSTEST_ENV_SYMBOL_KEY` symbol store) resolve in browser tests the same
|
|
839
|
+
// way they do in Node mode, where `prepare.ts` sets them on the real
|
|
840
|
+
// `process.env`.
|
|
841
|
+
// User-supplied `env` wins so explicit overrides still take effect.
|
|
842
|
+
// See https://github.com/web-infra-dev/rstest/issues/1351
|
|
836
843
|
env: {
|
|
837
844
|
NODE_ENV: process.env.NODE_ENV,
|
|
845
|
+
RSTEST: 'true',
|
|
838
846
|
...env,
|
|
839
847
|
},
|
|
840
848
|
testNamePattern,
|
|
@@ -864,7 +872,7 @@ const getRuntimeConfigFromProject = (
|
|
|
864
872
|
};
|
|
865
873
|
};
|
|
866
874
|
|
|
867
|
-
const getBrowserProjects = (context:
|
|
875
|
+
const getBrowserProjects = (context: RstestContext): ProjectContext[] => {
|
|
868
876
|
return context.projects.filter(
|
|
869
877
|
(project) => project.normalizedConfig.browser.enabled,
|
|
870
878
|
);
|
|
@@ -936,7 +944,7 @@ const resolveProviderForTestPath = ({
|
|
|
936
944
|
};
|
|
937
945
|
|
|
938
946
|
const collectProjectEntries = async (
|
|
939
|
-
context:
|
|
947
|
+
context: RstestContext,
|
|
940
948
|
): Promise<BrowserProjectEntries[]> => {
|
|
941
949
|
// Only collect entries for browser mode projects
|
|
942
950
|
const browserProjects = getBrowserProjects(context);
|
|
@@ -1211,7 +1219,7 @@ const createBrowserRuntime = async ({
|
|
|
1211
1219
|
containerDevServer,
|
|
1212
1220
|
forceHeadless,
|
|
1213
1221
|
}: {
|
|
1214
|
-
context:
|
|
1222
|
+
context: RstestContext;
|
|
1215
1223
|
manifestPath: string;
|
|
1216
1224
|
manifestSource: string;
|
|
1217
1225
|
tempDir: string;
|
|
@@ -1257,7 +1265,7 @@ const createBrowserRuntime = async ({
|
|
|
1257
1265
|
|
|
1258
1266
|
// Rstest internal aliases that must not be overridden by user config
|
|
1259
1267
|
const browserRuntimePath = fileURLToPath(
|
|
1260
|
-
import.meta.resolve('@rstest/core/browser-runtime'),
|
|
1268
|
+
import.meta.resolve('@rstest/core/internal/browser-runtime'),
|
|
1261
1269
|
);
|
|
1262
1270
|
|
|
1263
1271
|
const rstestInternalAliases = {
|
|
@@ -1268,7 +1276,7 @@ const createBrowserRuntime = async ({
|
|
|
1268
1276
|
'@rstest/browser': resolveBrowserFile('browser.ts'),
|
|
1269
1277
|
// Browser runtime APIs for entry.ts and public.ts
|
|
1270
1278
|
// Uses dist file with extractSourceMap to preserve sourcemap chain for inline snapshots
|
|
1271
|
-
'@rstest/core/browser-runtime': browserRuntimePath,
|
|
1279
|
+
'@rstest/core/internal/browser-runtime': browserRuntimePath,
|
|
1272
1280
|
'@sinonjs/fake-timers': resolveBrowserFile('client/fakeTimersStub.ts'),
|
|
1273
1281
|
};
|
|
1274
1282
|
|
|
@@ -1329,6 +1337,13 @@ const createBrowserRuntime = async ({
|
|
|
1329
1337
|
project.rootPath,
|
|
1330
1338
|
),
|
|
1331
1339
|
);
|
|
1340
|
+
// rspack `define` replaces `process.env` / `import.meta.env` with
|
|
1341
|
+
// this literal expression. JSON.stringify reproduces the exact
|
|
1342
|
+
// double-quoted `"rstest.env"` text, so the owned key can never
|
|
1343
|
+
// drift from the runtime `Symbol.for(RSTEST_ENV_SYMBOL_KEY)` sites.
|
|
1344
|
+
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(
|
|
1345
|
+
RSTEST_ENV_SYMBOL_KEY,
|
|
1346
|
+
)})]`;
|
|
1332
1347
|
// Merge order: current config -> userConfig -> rstest required config (highest priority)
|
|
1333
1348
|
const merged = mergeEnvironmentConfig(
|
|
1334
1349
|
config,
|
|
@@ -1347,8 +1362,8 @@ const createBrowserRuntime = async ({
|
|
|
1347
1362
|
},
|
|
1348
1363
|
source: {
|
|
1349
1364
|
define: {
|
|
1350
|
-
'process.env':
|
|
1351
|
-
'import.meta.env':
|
|
1365
|
+
'process.env': rstestEnvDefine,
|
|
1366
|
+
'import.meta.env': rstestEnvDefine,
|
|
1352
1367
|
},
|
|
1353
1368
|
},
|
|
1354
1369
|
output: {
|
|
@@ -1677,7 +1692,7 @@ const createBrowserRuntime = async ({
|
|
|
1677
1692
|
};
|
|
1678
1693
|
|
|
1679
1694
|
async function resolveProjectEntries(
|
|
1680
|
-
context:
|
|
1695
|
+
context: RstestContext,
|
|
1681
1696
|
shardedEntries?: Map<string, { entries: Record<string, string> }>,
|
|
1682
1697
|
): Promise<BrowserProjectEntries[]> {
|
|
1683
1698
|
if (shardedEntries) {
|
|
@@ -1707,7 +1722,7 @@ async function resolveProjectEntries(
|
|
|
1707
1722
|
// ============================================================================
|
|
1708
1723
|
|
|
1709
1724
|
export const runBrowserController = async (
|
|
1710
|
-
context:
|
|
1725
|
+
context: RstestContext,
|
|
1711
1726
|
options?: BrowserTestRunOptions,
|
|
1712
1727
|
): Promise<BrowserTestRunResult | void> => {
|
|
1713
1728
|
const {
|
|
@@ -2073,7 +2088,9 @@ export const runBrowserController = async (
|
|
|
2073
2088
|
|
|
2074
2089
|
// Get max testTimeout from all browser projects for RPC timeout
|
|
2075
2090
|
const maxTestTimeoutForRpc = Math.max(
|
|
2076
|
-
...browserProjects.map(
|
|
2091
|
+
...browserProjects.map(
|
|
2092
|
+
(p) => p.normalizedConfig.testTimeout ?? DEFAULT_TEST_TIMEOUT,
|
|
2093
|
+
),
|
|
2077
2094
|
);
|
|
2078
2095
|
|
|
2079
2096
|
const hostOptions: BrowserHostConfig = {
|
|
@@ -2161,13 +2178,16 @@ export const runBrowserController = async (
|
|
|
2161
2178
|
}
|
|
2162
2179
|
};
|
|
2163
2180
|
|
|
2164
|
-
runtime.dispatchHandlers.set(
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
request
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2181
|
+
runtime.dispatchHandlers.set(
|
|
2182
|
+
DISPATCH_NAMESPACE_BROWSER,
|
|
2183
|
+
async (dispatchRequest) => {
|
|
2184
|
+
const request = validateBrowserRpcRequest(dispatchRequest.args);
|
|
2185
|
+
return dispatchBrowserRpcRequest({
|
|
2186
|
+
request,
|
|
2187
|
+
target: dispatchRequest.target,
|
|
2188
|
+
});
|
|
2189
|
+
},
|
|
2190
|
+
);
|
|
2171
2191
|
|
|
2172
2192
|
runtime.setContainerOptions(hostOptions);
|
|
2173
2193
|
|
|
@@ -2397,7 +2417,9 @@ export const runBrowserController = async (
|
|
|
2397
2417
|
};
|
|
2398
2418
|
|
|
2399
2419
|
const shouldEmitUserConsoleLog = (log: UserConsoleLog): boolean => {
|
|
2400
|
-
return
|
|
2420
|
+
return (
|
|
2421
|
+
context.normalizedConfig.onConsoleLog?.(log.content, log.type) !== false
|
|
2422
|
+
);
|
|
2401
2423
|
};
|
|
2402
2424
|
|
|
2403
2425
|
const emitUserConsoleLog = async (log: UserConsoleLog): Promise<void> => {
|
|
@@ -2505,8 +2527,12 @@ export const runBrowserController = async (
|
|
|
2505
2527
|
);
|
|
2506
2528
|
case 'removeSnapshotFile':
|
|
2507
2529
|
return snapshotRpcMethods.removeSnapshotFile(request.args.filepath);
|
|
2508
|
-
default:
|
|
2509
|
-
|
|
2530
|
+
default: {
|
|
2531
|
+
// Exhaustiveness guard: a new SnapshotRpcRequest method without a case
|
|
2532
|
+
// here fails to compile rather than silently returning undefined.
|
|
2533
|
+
const _exhaustive: never = request;
|
|
2534
|
+
return _exhaustive;
|
|
2535
|
+
}
|
|
2510
2536
|
}
|
|
2511
2537
|
};
|
|
2512
2538
|
|
|
@@ -2596,7 +2622,7 @@ export const runBrowserController = async (
|
|
|
2596
2622
|
message: BrowserClientMessage,
|
|
2597
2623
|
): Promise<void> => {
|
|
2598
2624
|
const response = await dispatchRouter.dispatch({
|
|
2599
|
-
requestId: nextDispatchRequestId(
|
|
2625
|
+
requestId: nextDispatchRequestId(DISPATCH_NAMESPACE_RUNNER),
|
|
2600
2626
|
runToken: run.token,
|
|
2601
2627
|
namespace: DISPATCH_NAMESPACE_RUNNER,
|
|
2602
2628
|
method: message.type,
|
|
@@ -2653,7 +2679,7 @@ export const runBrowserController = async (
|
|
|
2653
2679
|
);
|
|
2654
2680
|
const perFileTimeoutMs =
|
|
2655
2681
|
(projectRuntime?.runtimeConfig.testTimeout ?? maxTestTimeoutForRpc) +
|
|
2656
|
-
|
|
2682
|
+
PER_FILE_TIMEOUT_BUFFER_MS;
|
|
2657
2683
|
|
|
2658
2684
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
2659
2685
|
|
|
@@ -3052,6 +3078,12 @@ export const runBrowserController = async (
|
|
|
3052
3078
|
}
|
|
3053
3079
|
|
|
3054
3080
|
let currentTestFiles = allTestFiles;
|
|
3081
|
+
// Coincidentally equal to the runner-side CONFIG_WAIT_TIMEOUT_MS and
|
|
3082
|
+
// DEFAULT_RPC_TIMEOUT_MS (client/entry.ts, client/dispatchTransport.ts) but
|
|
3083
|
+
// semantically distinct and in a different runtime, so deliberately NOT shared
|
|
3084
|
+
// with them. Invariant worth preserving: a runner must be able to receive its
|
|
3085
|
+
// config (config-wait) before the host declares its frames un-ready, i.e.
|
|
3086
|
+
// CONFIG_WAIT_TIMEOUT_MS <= RUNNER_FRAMES_READY_TIMEOUT_MS.
|
|
3055
3087
|
const RUNNER_FRAMES_READY_TIMEOUT_MS = 30_000;
|
|
3056
3088
|
let currentRunnerFramesSignature: string | null = null;
|
|
3057
3089
|
const runnerFramesWaiters = new Map<string, Set<() => void>>();
|
|
@@ -3139,7 +3171,7 @@ export const runBrowserController = async (
|
|
|
3139
3171
|
);
|
|
3140
3172
|
return (
|
|
3141
3173
|
(projectRuntime?.runtimeConfig.testTimeout ?? maxTestTimeoutForRpc) +
|
|
3142
|
-
|
|
3174
|
+
PER_FILE_TIMEOUT_BUFFER_MS
|
|
3143
3175
|
);
|
|
3144
3176
|
};
|
|
3145
3177
|
|
|
@@ -3602,7 +3634,7 @@ export type ListBrowserTestsResult = {
|
|
|
3602
3634
|
* and collects their test structure (describe/test declarations).
|
|
3603
3635
|
*/
|
|
3604
3636
|
export const listBrowserTests = async (
|
|
3605
|
-
context:
|
|
3637
|
+
context: RstestContext,
|
|
3606
3638
|
options?: {
|
|
3607
3639
|
shardedEntries?: Map<string, { entries: Record<string, string> }>;
|
|
3608
3640
|
},
|
|
@@ -3681,7 +3713,9 @@ export const listBrowserTests = async (
|
|
|
3681
3713
|
|
|
3682
3714
|
// Get max testTimeout from all browser projects for RPC timeout
|
|
3683
3715
|
const maxTestTimeoutForRpc = Math.max(
|
|
3684
|
-
...browserProjects.map(
|
|
3716
|
+
...browserProjects.map(
|
|
3717
|
+
(p) => p.normalizedConfig.testTimeout ?? DEFAULT_TEST_TIMEOUT,
|
|
3718
|
+
),
|
|
3685
3719
|
);
|
|
3686
3720
|
|
|
3687
3721
|
const hostOptions: BrowserHostConfig = {
|
package/src/index.ts
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BrowserHostModule,
|
|
2
3
|
BrowserTestRunOptions,
|
|
3
4
|
BrowserTestRunResult,
|
|
4
|
-
|
|
5
|
-
} from '@rstest/core/browser';
|
|
5
|
+
RstestContext,
|
|
6
|
+
} from '@rstest/core/internal/browser';
|
|
7
|
+
import { validateBrowserConfig } from './configValidation';
|
|
6
8
|
import {
|
|
7
9
|
type ListBrowserTestsResult,
|
|
8
10
|
listBrowserTests as listBrowserTestsImpl,
|
|
9
11
|
runBrowserController,
|
|
10
12
|
} from './hostController';
|
|
11
13
|
|
|
12
|
-
export { validateBrowserConfig }
|
|
13
|
-
export {
|
|
14
|
-
BROWSER_VIEWPORT_PRESET_DIMENSIONS,
|
|
15
|
-
BROWSER_VIEWPORT_PRESET_IDS,
|
|
16
|
-
resolveBrowserViewportPreset,
|
|
17
|
-
} from './viewportPresets';
|
|
14
|
+
export { validateBrowserConfig };
|
|
18
15
|
|
|
19
16
|
export async function runBrowserTests(
|
|
20
|
-
context:
|
|
17
|
+
context: RstestContext,
|
|
21
18
|
options?: BrowserTestRunOptions,
|
|
22
19
|
): Promise<BrowserTestRunResult | void> {
|
|
23
20
|
return runBrowserController(context, options);
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
export async function listBrowserTests(
|
|
27
|
-
context:
|
|
24
|
+
context: RstestContext,
|
|
25
|
+
options?: Pick<BrowserTestRunOptions, 'shardedEntries'>,
|
|
28
26
|
): Promise<ListBrowserTestsResult> {
|
|
29
|
-
|
|
27
|
+
// Forward `options` (e.g. `shardedEntries`) so `rstest list --shard` lists
|
|
28
|
+
// only the current shard's browser test files, matching the run path.
|
|
29
|
+
return listBrowserTestsImpl(context, options);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Compile-time guard: ensure the public host exports satisfy the core-owned
|
|
34
|
+
* {@link BrowserHostModule} contract. This catches drift such as a dropped
|
|
35
|
+
* `options` argument at the load boundary. No runtime side effect.
|
|
36
|
+
*/
|
|
37
|
+
void ({
|
|
38
|
+
validateBrowserConfig,
|
|
39
|
+
runBrowserTests,
|
|
40
|
+
listBrowserTests,
|
|
41
|
+
} satisfies BrowserHostModule);
|
|
42
|
+
|
|
32
43
|
export type {
|
|
33
44
|
BrowserTestRunOptions,
|
|
34
45
|
BrowserTestRunResult,
|