@rstest/browser 0.11.6 → 0.11.8
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/browser-container/container-static/css/index.43daaf6c93.css +1 -0
- package/dist/browser-container/container-static/js/260.3739fe815c.js +194 -0
- package/dist/browser-container/container-static/js/index.c0f87bfb3e.js +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browserRsbuild.d.ts +24 -4
- package/dist/browserV8Coverage.d.ts +15 -0
- package/dist/client/240~1.js +150 -0
- package/dist/{augmentExpect.d.ts → client/client/augmentExpect.d.ts} +1 -1
- package/dist/client/{browserRpc.d.ts → client/browserRpc.d.ts} +1 -1
- package/dist/client/{dispatchTransport.d.ts → client/dispatchTransport.d.ts} +8 -4
- package/dist/client/client/index.d.ts +2 -0
- package/dist/client/client/runIdentity.d.ts +12 -0
- package/dist/client/client/runner.d.ts +4 -0
- package/dist/client/client/snapshot.d.ts +27 -0
- package/dist/client/client/sourceMapSupport.d.ts +36 -0
- package/dist/{browser.js → client/index.js} +3 -102
- package/dist/client/protocol.d.ts +225 -0
- package/dist/client/rpcProtocol.d.ts +138 -0
- package/dist/client/runner.js +675 -0
- package/dist/client/sourceMap/sourceMapLoader.d.ts +25 -0
- package/dist/containerRpc.d.ts +27 -19
- package/dist/headedRunRegistry.d.ts +51 -0
- package/dist/headedScheduler.d.ts +10 -10
- package/dist/headlessScheduler.d.ts +5 -4
- package/dist/headlessTransport.d.ts +4 -2
- package/dist/hostPayloads.d.ts +1 -7
- package/dist/index.js +840 -330
- package/dist/protocol.d.ts +48 -8
- package/dist/providers/index.d.ts +21 -0
- package/dist/providers/playwright/v8Coverage.d.ts +2 -0
- package/dist/rpcProtocol.d.ts +1 -2
- package/dist/schedulerSeam.d.ts +5 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +12 -1
- package/dist/watchRerunPlanner.d.ts +20 -9
- package/dist/watchSignals.d.ts +8 -1
- package/package.json +11 -11
- package/dist/33.js +0 -7
- package/dist/browser-container/container-static/css/index.0521ff95b1.css +0 -1
- package/dist/browser-container/container-static/js/683.4f821ce8f1.js +0 -194
- package/dist/browser-container/container-static/js/index.568aa7cb35.js +0 -1
- package/dist/browser.d.ts +0 -2
- package/src/augmentExpect.ts +0 -62
- package/src/browser.ts +0 -3
- package/src/browserExecutor.ts +0 -222
- package/src/browserRpcRegistry.ts +0 -57
- package/src/browserRsbuild.ts +0 -1927
- package/src/client/api.ts +0 -213
- package/src/client/browserRpc.ts +0 -86
- package/src/client/dispatchTransport.ts +0 -240
- package/src/client/entry.ts +0 -807
- package/src/client/locator.ts +0 -448
- package/src/client/snapshot.ts +0 -113
- package/src/client/sourceMapSupport.ts +0 -159
- package/src/concurrency.ts +0 -65
- package/src/configValidation.ts +0 -263
- package/src/containerRpc.ts +0 -206
- package/src/dispatchCapabilities.ts +0 -177
- package/src/dispatchRouter.ts +0 -82
- package/src/env.d.ts +0 -54
- package/src/headedScheduler.ts +0 -664
- package/src/headedSerialTaskQueue.ts +0 -23
- package/src/headlessScheduler.ts +0 -566
- package/src/headlessTransport.ts +0 -28
- package/src/hostController.ts +0 -1365
- package/src/hostPayloads.ts +0 -62
- package/src/index.ts +0 -51
- package/src/manifest.d.ts +0 -41
- package/src/protocol.ts +0 -225
- package/src/providers/index.ts +0 -126
- package/src/providers/playwright/compileLocator.ts +0 -130
- package/src/providers/playwright/dispatchBrowserRpc.ts +0 -372
- package/src/providers/playwright/expectUtils.ts +0 -57
- package/src/providers/playwright/implementation.ts +0 -35
- package/src/providers/playwright/index.ts +0 -1
- package/src/providers/playwright/runtime.ts +0 -159
- package/src/providers/playwright/textMatcher.ts +0 -10
- package/src/rpcProtocol.ts +0 -218
- package/src/runSession.ts +0 -110
- package/src/schedulerSeam.ts +0 -49
- package/src/sessionRegistry.ts +0 -89
- package/src/sourceMap/sourceMapLoader.ts +0 -96
- package/src/viewportPresets.ts +0 -69
- package/src/watchRerunPlanner.ts +0 -88
- package/src/watchRuntime.ts +0 -83
- package/src/watchSignals.ts +0 -93
- /package/dist/{rslib-runtime.js → 612~0.js} +0 -0
- /package/dist/client/{api.d.ts → client/api.d.ts} +0 -0
- /package/dist/client/{locator.d.ts → client/locator.d.ts} +0 -0
package/src/client/entry.ts
DELETED
|
@@ -1,807 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ManifestProjectConfig,
|
|
3
|
-
type ManifestTestContext,
|
|
4
|
-
projectSetupLoaders,
|
|
5
|
-
// Multi-project APIs
|
|
6
|
-
projects,
|
|
7
|
-
projectTestContexts,
|
|
8
|
-
} from '@rstest/browser-manifest';
|
|
9
|
-
import type {
|
|
10
|
-
CoverageMapData,
|
|
11
|
-
CurrentTaskInfo,
|
|
12
|
-
RunnerHooks,
|
|
13
|
-
RuntimeConfig,
|
|
14
|
-
WorkerState,
|
|
15
|
-
} from '@rstest/core/internal/browser-runtime';
|
|
16
|
-
import {
|
|
17
|
-
createBrowserTaskContext,
|
|
18
|
-
createRstestRuntime,
|
|
19
|
-
formatConsoleArgs,
|
|
20
|
-
globalApis,
|
|
21
|
-
RSTEST_API_GLOBAL_KEY,
|
|
22
|
-
RSTEST_ENV_SYMBOL_KEY,
|
|
23
|
-
setRealTimers,
|
|
24
|
-
unwrapRegex,
|
|
25
|
-
} from '@rstest/core/internal/browser-runtime';
|
|
26
|
-
import { normalize } from 'pathe';
|
|
27
|
-
import type {
|
|
28
|
-
BrowserClientMessage,
|
|
29
|
-
BrowserProjectRuntime,
|
|
30
|
-
RunnerLifecycleMethod,
|
|
31
|
-
} from '../protocol';
|
|
32
|
-
import { DISPATCH_MESSAGE_TYPE, RSTEST_CONFIG_MESSAGE_TYPE } from '../protocol';
|
|
33
|
-
import {
|
|
34
|
-
createRunnerLifecycleRequest,
|
|
35
|
-
sendRunnerLifecycle,
|
|
36
|
-
} from './dispatchTransport';
|
|
37
|
-
import { BrowserSnapshotEnvironment } from './snapshot';
|
|
38
|
-
import {
|
|
39
|
-
findNewScriptUrl,
|
|
40
|
-
getScriptUrls,
|
|
41
|
-
preloadRunnerSourceMap,
|
|
42
|
-
preloadTestFileSourceMap,
|
|
43
|
-
} from './sourceMapSupport';
|
|
44
|
-
|
|
45
|
-
declare global {
|
|
46
|
-
// eslint-disable-next-line no-var
|
|
47
|
-
var __coverage__: Record<string, unknown> | undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Debug logger for browser client.
|
|
52
|
-
* Only logs when debug mode is enabled (DEBUG=rstest on server side).
|
|
53
|
-
*/
|
|
54
|
-
const debugLog = (...args: unknown[]): void => {
|
|
55
|
-
if (window.__RSTEST_BROWSER_OPTIONS__?.debug) {
|
|
56
|
-
console.log(...args);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
type RuntimeEnvStore = Record<string, string | undefined>;
|
|
61
|
-
const RSTEST_ENV_SYMBOL = Symbol.for(RSTEST_ENV_SYMBOL_KEY);
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Publish the runtime API on the globals test modules read: the
|
|
65
|
-
* `@rstest/core` external and the `import.meta.rstest` define (node parity:
|
|
66
|
-
* `global['@rstest/core']` in runInPool), plus the `globals: true` API names.
|
|
67
|
-
*/
|
|
68
|
-
const installRuntimeGlobals = (
|
|
69
|
-
runtime: Awaited<ReturnType<typeof createRstestRuntime>>,
|
|
70
|
-
runtimeConfig: RuntimeConfig,
|
|
71
|
-
): void => {
|
|
72
|
-
(globalThis as Record<string, unknown>)[RSTEST_API_GLOBAL_KEY] = runtime.api;
|
|
73
|
-
if (runtimeConfig.globals) {
|
|
74
|
-
for (const apiKey of globalApis) {
|
|
75
|
-
(globalThis as any)[apiKey] = (runtime.api as any)[apiKey];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
type GlobalWithRuntimeEnv = typeof globalThis &
|
|
81
|
-
Record<symbol, unknown> & {
|
|
82
|
-
global?: typeof globalThis;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const restoreRuntimeConfig = (
|
|
86
|
-
config: BrowserProjectRuntime['runtimeConfig'],
|
|
87
|
-
): RuntimeConfig => {
|
|
88
|
-
const { testNamePattern } = config;
|
|
89
|
-
// The browser wire (BrowserRuntimeConfig) omits node-only fields
|
|
90
|
-
// (testEnvironment / coverage / logHeapUsage / detectAsyncLeaks) that the
|
|
91
|
-
// browser runtime never reads. The shared WorkerState / runner types require
|
|
92
|
-
// the full RuntimeConfig shape and runner heap sampling is guarded against
|
|
93
|
-
// the absent logHeapUsage, so widening back here is sound.
|
|
94
|
-
return {
|
|
95
|
-
...config,
|
|
96
|
-
testNamePattern:
|
|
97
|
-
typeof testNamePattern === 'string'
|
|
98
|
-
? unwrapRegex(testNamePattern)
|
|
99
|
-
: testNamePattern,
|
|
100
|
-
} as RuntimeConfig;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const ensureRuntimeEnv = (env: RuntimeConfig['env'] | undefined): void => {
|
|
104
|
-
const globalRef = globalThis as GlobalWithRuntimeEnv;
|
|
105
|
-
if (!globalRef.global) {
|
|
106
|
-
globalRef.global = globalRef;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const existingEnv = globalRef[RSTEST_ENV_SYMBOL];
|
|
110
|
-
let runtimeEnv: RuntimeEnvStore;
|
|
111
|
-
if (existingEnv && typeof existingEnv === 'object') {
|
|
112
|
-
runtimeEnv = existingEnv as RuntimeEnvStore;
|
|
113
|
-
} else {
|
|
114
|
-
runtimeEnv = {};
|
|
115
|
-
globalRef[RSTEST_ENV_SYMBOL] = runtimeEnv;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (env) {
|
|
119
|
-
for (const [key, value] of Object.entries(env)) {
|
|
120
|
-
const normalizedValue =
|
|
121
|
-
typeof value === 'string'
|
|
122
|
-
? value
|
|
123
|
-
: value == null
|
|
124
|
-
? undefined
|
|
125
|
-
: String(value);
|
|
126
|
-
|
|
127
|
-
if (normalizedValue === undefined) {
|
|
128
|
-
delete runtimeEnv[key];
|
|
129
|
-
} else {
|
|
130
|
-
runtimeEnv[key] = normalizedValue;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const getFileTaskId = (testPath: string): string => {
|
|
137
|
-
return `file:${testPath}`;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Intercept console methods and forward to host via send().
|
|
142
|
-
* Returns a restore function to revert console to original.
|
|
143
|
-
*/
|
|
144
|
-
const interceptConsole = (
|
|
145
|
-
projectName: string,
|
|
146
|
-
getCurrentTask: () => CurrentTaskInfo | undefined,
|
|
147
|
-
printConsoleTrace: boolean,
|
|
148
|
-
): (() => void) => {
|
|
149
|
-
const originalConsole = {
|
|
150
|
-
log: console.log.bind(console),
|
|
151
|
-
warn: console.warn.bind(console),
|
|
152
|
-
error: console.error.bind(console),
|
|
153
|
-
info: console.info.bind(console),
|
|
154
|
-
debug: console.debug.bind(console),
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const getConsoleTrace = (): string | undefined => {
|
|
158
|
-
if (!printConsoleTrace) return undefined;
|
|
159
|
-
const stack = new Error('STACK_TRACE').stack;
|
|
160
|
-
// Skip: Error, getConsoleTrace, createConsoleInterceptor wrapper, console.log call
|
|
161
|
-
return stack?.split('\n').slice(4).join('\n');
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const createConsoleInterceptor = (
|
|
165
|
-
level: 'log' | 'warn' | 'error' | 'info' | 'debug',
|
|
166
|
-
) => {
|
|
167
|
-
return (...args: unknown[]) => {
|
|
168
|
-
// Call original for browser DevTools
|
|
169
|
-
originalConsole[level](...args);
|
|
170
|
-
|
|
171
|
-
const content = formatConsoleArgs(args);
|
|
172
|
-
const currentTask = getCurrentTask();
|
|
173
|
-
|
|
174
|
-
// Send to host
|
|
175
|
-
send({
|
|
176
|
-
type: 'log',
|
|
177
|
-
payload: {
|
|
178
|
-
level,
|
|
179
|
-
content,
|
|
180
|
-
projectName,
|
|
181
|
-
taskId: currentTask?.taskId,
|
|
182
|
-
taskName: currentTask?.taskName,
|
|
183
|
-
taskParentNames: currentTask?.taskParentNames,
|
|
184
|
-
taskType: currentTask?.taskType,
|
|
185
|
-
testPath: currentTask?.testPath ?? '',
|
|
186
|
-
type: level === 'error' || level === 'warn' ? 'stderr' : 'stdout',
|
|
187
|
-
trace: getConsoleTrace(),
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
console.log = createConsoleInterceptor('log');
|
|
194
|
-
console.warn = createConsoleInterceptor('warn');
|
|
195
|
-
console.error = createConsoleInterceptor('error');
|
|
196
|
-
console.info = createConsoleInterceptor('info');
|
|
197
|
-
console.debug = createConsoleInterceptor('debug');
|
|
198
|
-
|
|
199
|
-
return () => {
|
|
200
|
-
console.log = originalConsole.log;
|
|
201
|
-
console.warn = originalConsole.warn;
|
|
202
|
-
console.error = originalConsole.error;
|
|
203
|
-
console.info = originalConsole.info;
|
|
204
|
-
console.debug = originalConsole.debug;
|
|
205
|
-
};
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const send = (message: BrowserClientMessage): void => {
|
|
209
|
-
// If in iframe, send to parent window (container) which will forward to host via RPC
|
|
210
|
-
if (window.parent !== window) {
|
|
211
|
-
window.parent.postMessage(
|
|
212
|
-
{ type: DISPATCH_MESSAGE_TYPE, payload: message },
|
|
213
|
-
'*',
|
|
214
|
-
);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
// Fallback: direct call if running outside iframe (not typical)
|
|
218
|
-
// Note: This binding may not exist if not using Playwright
|
|
219
|
-
window[DISPATCH_MESSAGE_TYPE]?.(message);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const dispatchRunnerLifecycle = (
|
|
223
|
-
method: RunnerLifecycleMethod,
|
|
224
|
-
payload: unknown,
|
|
225
|
-
): void => {
|
|
226
|
-
sendRunnerLifecycle(
|
|
227
|
-
createRunnerLifecycleRequest(method, payload),
|
|
228
|
-
(error: unknown) => {
|
|
229
|
-
debugLog('[Runner] Failed to dispatch lifecycle method:', method, error);
|
|
230
|
-
},
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Timeout for waiting for browser config from container (30 seconds).
|
|
236
|
-
*
|
|
237
|
-
* Coincidentally equal to the RPC default (client/dispatchTransport.ts) and the
|
|
238
|
-
* host's RUNNER_FRAMES_READY_TIMEOUT_MS (hostController.ts), but semantically
|
|
239
|
-
* distinct and in a different runtime, so deliberately not shared. Implicit
|
|
240
|
-
* invariant: this must not exceed the host's frames-ready timeout, or the host
|
|
241
|
-
* declares the runner un-ready before it can even receive its config.
|
|
242
|
-
*/
|
|
243
|
-
const CONFIG_WAIT_TIMEOUT_MS = 30_000;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Wait for configuration from container if running in iframe.
|
|
247
|
-
* This is a prerequisite for test execution - without config, tests cannot run.
|
|
248
|
-
*/
|
|
249
|
-
const waitForConfig = (): Promise<void> => {
|
|
250
|
-
// If not in iframe or already has config, resolve immediately
|
|
251
|
-
if (window.parent === window || window.__RSTEST_BROWSER_OPTIONS__) {
|
|
252
|
-
return Promise.resolve();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return new Promise((resolve, reject) => {
|
|
256
|
-
const handleMessage = (event: MessageEvent) => {
|
|
257
|
-
if (event.data?.type === RSTEST_CONFIG_MESSAGE_TYPE) {
|
|
258
|
-
window.__RSTEST_BROWSER_OPTIONS__ = event.data.payload;
|
|
259
|
-
debugLog(
|
|
260
|
-
'[Runner] Received config from container:',
|
|
261
|
-
event.data.payload,
|
|
262
|
-
);
|
|
263
|
-
window.removeEventListener('message', handleMessage);
|
|
264
|
-
resolve();
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
window.addEventListener('message', handleMessage);
|
|
269
|
-
|
|
270
|
-
setTimeout(() => {
|
|
271
|
-
window.removeEventListener('message', handleMessage);
|
|
272
|
-
reject(
|
|
273
|
-
new Error(
|
|
274
|
-
`[Rstest] Failed to receive browser config within ${CONFIG_WAIT_TIMEOUT_MS / 1000}s. ` +
|
|
275
|
-
'This may indicate a connection issue between the runner iframe and container.',
|
|
276
|
-
),
|
|
277
|
-
);
|
|
278
|
-
}, CONFIG_WAIT_TIMEOUT_MS);
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Convert absolute path to context key (relative path)
|
|
284
|
-
* e.g., '/project/src/foo.test.ts' -> './src/foo.test.ts'
|
|
285
|
-
* 'D:/project/src/foo.test.ts' -> './src/foo.test.ts'
|
|
286
|
-
*
|
|
287
|
-
* Uses pathe's normalize to handle cross-platform path separators.
|
|
288
|
-
*/
|
|
289
|
-
const toContextKey = (absolutePath: string, projectRoot: string): string => {
|
|
290
|
-
// Normalize both paths to use forward slashes for cross-platform compatibility
|
|
291
|
-
const normalizedAbsolute = normalize(absolutePath);
|
|
292
|
-
const normalizedRoot = normalize(projectRoot);
|
|
293
|
-
|
|
294
|
-
// Only strip the root at a path boundary: a bare `startsWith` would mangle a
|
|
295
|
-
// sibling like `/repo/pkg-extra/a.test.ts` under root `/repo/pkg`. Must stay
|
|
296
|
-
// in sync with the host `toContextKey` (hostController.ts) so the non-watch
|
|
297
|
-
// import-map keys resolve.
|
|
298
|
-
const withinRoot =
|
|
299
|
-
normalizedAbsolute === normalizedRoot ||
|
|
300
|
-
normalizedAbsolute.startsWith(`${normalizedRoot}/`);
|
|
301
|
-
if (!withinRoot) {
|
|
302
|
-
// Test file outside the project root: keep the absolute path as the key so
|
|
303
|
-
// `toAbsolutePath` round-trips it instead of re-rooting under projectRoot.
|
|
304
|
-
return normalizedAbsolute;
|
|
305
|
-
}
|
|
306
|
-
const relative = normalizedAbsolute.slice(normalizedRoot.length);
|
|
307
|
-
return relative.startsWith('/') ? `.${relative}` : `./${relative}`;
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Convert context key to absolute path
|
|
312
|
-
* e.g., './src/foo.test.ts' -> '/project/src/foo.test.ts'
|
|
313
|
-
*/
|
|
314
|
-
const toAbsolutePath = (key: string, projectRoot: string): string => {
|
|
315
|
-
// An absolute key (test file outside the project root, see `toContextKey`)
|
|
316
|
-
// round-trips as-is; only `./`-prefixed relative keys are re-rooted.
|
|
317
|
-
if (!key.startsWith('.')) {
|
|
318
|
-
return key;
|
|
319
|
-
}
|
|
320
|
-
// key format: ./src/foo.test.ts
|
|
321
|
-
// Ensure no double slashes by removing trailing slash from projectRoot
|
|
322
|
-
const normalizedRoot = normalize(projectRoot).replace(/\/$/, '');
|
|
323
|
-
return normalizedRoot + key.slice(1);
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Find the project that contains the given test file.
|
|
328
|
-
* Matches by checking if the testFile path starts with the project root.
|
|
329
|
-
*
|
|
330
|
-
* Uses pathe's normalize to handle cross-platform path separators.
|
|
331
|
-
*/
|
|
332
|
-
const findProjectForTestFile = (
|
|
333
|
-
testFile: string,
|
|
334
|
-
allProjects: ManifestProjectConfig[],
|
|
335
|
-
): ManifestProjectConfig | undefined => {
|
|
336
|
-
// Normalize the test file path for cross-platform compatibility
|
|
337
|
-
const normalizedTestFile = normalize(testFile);
|
|
338
|
-
|
|
339
|
-
// Sort projects by root path length (longest first) for most specific match
|
|
340
|
-
const sorted = [...allProjects].sort(
|
|
341
|
-
(a, b) => b.projectRoot.length - a.projectRoot.length,
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
for (const proj of sorted) {
|
|
345
|
-
// projectRoot should already be normalized, but normalize again for safety
|
|
346
|
-
const normalizedRoot = normalize(proj.projectRoot);
|
|
347
|
-
if (normalizedTestFile.startsWith(normalizedRoot)) {
|
|
348
|
-
return proj;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Fallback to first project
|
|
353
|
-
return allProjects[0];
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const run = async () => {
|
|
357
|
-
// Wait for configuration if in iframe
|
|
358
|
-
await waitForConfig();
|
|
359
|
-
let options = window.__RSTEST_BROWSER_OPTIONS__;
|
|
360
|
-
|
|
361
|
-
// Support reading testFile and testNamePattern from URL parameters
|
|
362
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
363
|
-
const urlTestFile = urlParams.get('testFile');
|
|
364
|
-
const urlRunId = urlParams.get('runId');
|
|
365
|
-
const urlTestNamePattern = urlParams.get('testNamePattern');
|
|
366
|
-
|
|
367
|
-
if (urlTestFile && options) {
|
|
368
|
-
// Override testFile from URL parameter
|
|
369
|
-
options = {
|
|
370
|
-
...options,
|
|
371
|
-
testFile: urlTestFile,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (urlRunId && options) {
|
|
376
|
-
options = {
|
|
377
|
-
...options,
|
|
378
|
-
runId: urlRunId,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Override testNamePattern from URL parameter if provided
|
|
383
|
-
if (urlTestNamePattern && options) {
|
|
384
|
-
options = {
|
|
385
|
-
...options,
|
|
386
|
-
projects: options.projects.map((project) => ({
|
|
387
|
-
...project,
|
|
388
|
-
runtimeConfig: {
|
|
389
|
-
...project.runtimeConfig,
|
|
390
|
-
testNamePattern: urlTestNamePattern,
|
|
391
|
-
},
|
|
392
|
-
})),
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (!options) {
|
|
397
|
-
send({
|
|
398
|
-
type: 'fatal',
|
|
399
|
-
payload: {
|
|
400
|
-
message: 'Browser test runtime is not configured.',
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
window.__RSTEST_DONE__ = true;
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
send({ type: 'ready' });
|
|
408
|
-
|
|
409
|
-
setRealTimers();
|
|
410
|
-
|
|
411
|
-
// Preload runner.js sourcemap for inline snapshot support.
|
|
412
|
-
// The snapshot code runs in runner.js, so we need its sourcemap
|
|
413
|
-
// to map stack traces back to original source files.
|
|
414
|
-
await preloadRunnerSourceMap();
|
|
415
|
-
|
|
416
|
-
// Find the project for this test file
|
|
417
|
-
const targetTestFile = options.testFile;
|
|
418
|
-
const currentProject = targetTestFile
|
|
419
|
-
? findProjectForTestFile(
|
|
420
|
-
targetTestFile,
|
|
421
|
-
projects as ManifestProjectConfig[],
|
|
422
|
-
)
|
|
423
|
-
: (projects as ManifestProjectConfig[])[0];
|
|
424
|
-
|
|
425
|
-
if (!currentProject) {
|
|
426
|
-
send({
|
|
427
|
-
type: 'fatal',
|
|
428
|
-
payload: {
|
|
429
|
-
message: 'No project found for test file',
|
|
430
|
-
},
|
|
431
|
-
});
|
|
432
|
-
window.__RSTEST_DONE__ = true;
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Find the runtime config for this project
|
|
437
|
-
const projectRuntime = options.projects.find(
|
|
438
|
-
(p) => p.name === currentProject.name,
|
|
439
|
-
);
|
|
440
|
-
if (!projectRuntime) {
|
|
441
|
-
send({
|
|
442
|
-
type: 'fatal',
|
|
443
|
-
payload: {
|
|
444
|
-
message: `Project ${currentProject.name} not found in runtime options`,
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
window.__RSTEST_DONE__ = true;
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const runtimeConfig = restoreRuntimeConfig(projectRuntime.runtimeConfig);
|
|
452
|
-
ensureRuntimeEnv(runtimeConfig.env);
|
|
453
|
-
|
|
454
|
-
// Get this project's setup loaders and test context
|
|
455
|
-
const currentSetupLoaders =
|
|
456
|
-
(projectSetupLoaders as Record<string, Array<() => Promise<unknown>>>)[
|
|
457
|
-
currentProject.name
|
|
458
|
-
] || [];
|
|
459
|
-
const currentTestContext = (
|
|
460
|
-
projectTestContexts as Record<string, ManifestTestContext>
|
|
461
|
-
)[currentProject.name];
|
|
462
|
-
|
|
463
|
-
if (!currentTestContext) {
|
|
464
|
-
send({
|
|
465
|
-
type: 'fatal',
|
|
466
|
-
payload: {
|
|
467
|
-
message: `Test context not found for project ${currentProject.name}`,
|
|
468
|
-
},
|
|
469
|
-
});
|
|
470
|
-
window.__RSTEST_DONE__ = true;
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const loadSetupFiles = async (): Promise<void> => {
|
|
475
|
-
for (const loadSetup of currentSetupLoaders) {
|
|
476
|
-
await loadSetup();
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
// 1. Determine which test files to run
|
|
481
|
-
let testKeysToRun: string[];
|
|
482
|
-
|
|
483
|
-
if (targetTestFile) {
|
|
484
|
-
// Single file mode: convert absolute path to context key
|
|
485
|
-
const key = toContextKey(targetTestFile, currentProject.projectRoot);
|
|
486
|
-
testKeysToRun = [key];
|
|
487
|
-
} else {
|
|
488
|
-
// Full run mode: get all test keys from context
|
|
489
|
-
testKeysToRun = currentTestContext.getTestKeys();
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Check execution mode
|
|
493
|
-
const executionMode = options.mode || 'run';
|
|
494
|
-
|
|
495
|
-
// Collect mode: only gather test metadata without running
|
|
496
|
-
if (executionMode === 'collect') {
|
|
497
|
-
for (const key of testKeysToRun) {
|
|
498
|
-
const testPath = toAbsolutePath(key, currentProject.projectRoot);
|
|
499
|
-
|
|
500
|
-
const workerState: WorkerState = {
|
|
501
|
-
project: projectRuntime.name,
|
|
502
|
-
projectRoot: projectRuntime.projectRoot,
|
|
503
|
-
rootPath: options.rootPath,
|
|
504
|
-
runtimeConfig,
|
|
505
|
-
taskId: 0,
|
|
506
|
-
// The kept-module-cache flush keyed on `buildId` is node worker-pool
|
|
507
|
-
// only (#1373); browser runners never reuse a node worker, so a constant
|
|
508
|
-
// inert id is correct here.
|
|
509
|
-
buildId: 0,
|
|
510
|
-
outputModule: false,
|
|
511
|
-
environment: 'browser',
|
|
512
|
-
testPath,
|
|
513
|
-
distPath: testPath,
|
|
514
|
-
snapshotOptions: {
|
|
515
|
-
updateSnapshot: options.snapshot.updateSnapshot,
|
|
516
|
-
snapshotEnvironment: new BrowserSnapshotEnvironment(),
|
|
517
|
-
snapshotFormat: runtimeConfig.snapshotFormat,
|
|
518
|
-
},
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
const runtime = await createRstestRuntime(workerState, {
|
|
522
|
-
taskContext: createBrowserTaskContext(),
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
installRuntimeGlobals(runtime, runtimeConfig);
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
// Load setup files for this project after runtime is ready.
|
|
529
|
-
await loadSetupFiles();
|
|
530
|
-
|
|
531
|
-
// Load the test file dynamically (registers tests without running)
|
|
532
|
-
await currentTestContext.loadTest(key);
|
|
533
|
-
|
|
534
|
-
// Collect tests metadata
|
|
535
|
-
const tests = await runtime.runner.collectTests();
|
|
536
|
-
|
|
537
|
-
send({
|
|
538
|
-
type: 'collect-result',
|
|
539
|
-
payload: {
|
|
540
|
-
testPath,
|
|
541
|
-
project: projectRuntime.name,
|
|
542
|
-
tests,
|
|
543
|
-
},
|
|
544
|
-
});
|
|
545
|
-
} catch (_error) {
|
|
546
|
-
const error =
|
|
547
|
-
_error instanceof Error ? _error : new Error(String(_error));
|
|
548
|
-
send({
|
|
549
|
-
type: 'fatal',
|
|
550
|
-
payload: {
|
|
551
|
-
message: error.message,
|
|
552
|
-
stack: error.stack,
|
|
553
|
-
},
|
|
554
|
-
});
|
|
555
|
-
window.__RSTEST_DONE__ = true;
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
send({ type: 'collect-complete' });
|
|
561
|
-
window.__RSTEST_DONE__ = true;
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Capture unhandled errors/rejections that escape a test file's execution.
|
|
566
|
-
// Parity with the node worker, which attaches process-level
|
|
567
|
-
// uncaughtException/unhandledRejection to the running file's result and fails
|
|
568
|
-
// the file. `activeUnhandledErrors` points at the currently running file's
|
|
569
|
-
// collector (undefined between files, so stray late events are ignored).
|
|
570
|
-
let activeUnhandledErrors: Error[] | undefined;
|
|
571
|
-
const onWindowError = (event: ErrorEvent): void => {
|
|
572
|
-
activeUnhandledErrors?.push(
|
|
573
|
-
event.error instanceof Error
|
|
574
|
-
? event.error
|
|
575
|
-
: new Error(event.message || String(event.error)),
|
|
576
|
-
);
|
|
577
|
-
};
|
|
578
|
-
const onUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
|
579
|
-
const { reason } = event;
|
|
580
|
-
activeUnhandledErrors?.push(
|
|
581
|
-
reason instanceof Error ? reason : new Error(String(reason)),
|
|
582
|
-
);
|
|
583
|
-
};
|
|
584
|
-
window.addEventListener('error', onWindowError);
|
|
585
|
-
window.addEventListener('unhandledrejection', onUnhandledRejection);
|
|
586
|
-
|
|
587
|
-
// 2. Run tests for each file
|
|
588
|
-
for (const key of testKeysToRun) {
|
|
589
|
-
const testPath = toAbsolutePath(key, currentProject.projectRoot);
|
|
590
|
-
const taskStack: CurrentTaskInfo[] = [
|
|
591
|
-
{
|
|
592
|
-
taskId: getFileTaskId(testPath),
|
|
593
|
-
taskType: 'file',
|
|
594
|
-
testPath,
|
|
595
|
-
},
|
|
596
|
-
];
|
|
597
|
-
|
|
598
|
-
// Per-file TaskContext; taskStack supplies the concurrent attribution
|
|
599
|
-
// that the single-slot fallback can't.
|
|
600
|
-
const taskContext = createBrowserTaskContext();
|
|
601
|
-
|
|
602
|
-
const shouldInterceptConsole =
|
|
603
|
-
!runtimeConfig.disableConsoleIntercept ||
|
|
604
|
-
runtimeConfig.silent === true ||
|
|
605
|
-
runtimeConfig.silent === 'passed-only';
|
|
606
|
-
|
|
607
|
-
// Intercept console methods to forward logs to host
|
|
608
|
-
const restoreConsole = shouldInterceptConsole
|
|
609
|
-
? interceptConsole(
|
|
610
|
-
projectRuntime.name,
|
|
611
|
-
() => taskContext.getCurrent() ?? taskStack[taskStack.length - 1],
|
|
612
|
-
runtimeConfig.disableConsoleIntercept
|
|
613
|
-
? false
|
|
614
|
-
: (runtimeConfig.printConsoleTrace ?? false),
|
|
615
|
-
)
|
|
616
|
-
: () => {};
|
|
617
|
-
|
|
618
|
-
const workerState: WorkerState = {
|
|
619
|
-
project: projectRuntime.name,
|
|
620
|
-
projectRoot: projectRuntime.projectRoot,
|
|
621
|
-
rootPath: options.rootPath,
|
|
622
|
-
runtimeConfig,
|
|
623
|
-
taskId: 0,
|
|
624
|
-
// See the `buildId` note above: inert in browser mode.
|
|
625
|
-
buildId: 0,
|
|
626
|
-
outputModule: false,
|
|
627
|
-
environment: 'browser',
|
|
628
|
-
currentTask: taskStack[0],
|
|
629
|
-
testPath,
|
|
630
|
-
distPath: testPath,
|
|
631
|
-
snapshotOptions: {
|
|
632
|
-
updateSnapshot: options.snapshot.updateSnapshot,
|
|
633
|
-
snapshotEnvironment: new BrowserSnapshotEnvironment(),
|
|
634
|
-
snapshotFormat: runtimeConfig.snapshotFormat,
|
|
635
|
-
},
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
const syncCurrentTask = (): void => {
|
|
639
|
-
workerState.currentTask = taskStack[taskStack.length - 1];
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
const removeTaskFromStack = (taskId: string): void => {
|
|
643
|
-
const taskIndex = taskStack.findLastIndex(
|
|
644
|
-
(task) => task.taskId === taskId,
|
|
645
|
-
);
|
|
646
|
-
if (taskIndex < 0) {
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
taskStack.splice(taskIndex, 1);
|
|
650
|
-
syncCurrentTask();
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
const runtime = await createRstestRuntime(workerState, { taskContext });
|
|
654
|
-
|
|
655
|
-
installRuntimeGlobals(runtime, runtimeConfig);
|
|
656
|
-
|
|
657
|
-
let failedTestsCount = 0;
|
|
658
|
-
|
|
659
|
-
const runnerHooks: RunnerHooks = {
|
|
660
|
-
onTestFileReady: async (test) => {
|
|
661
|
-
dispatchRunnerLifecycle('file-ready', test);
|
|
662
|
-
},
|
|
663
|
-
onTestSuiteStart: async (test) => {
|
|
664
|
-
taskStack.push({
|
|
665
|
-
taskId: test.testId,
|
|
666
|
-
taskName: test.name,
|
|
667
|
-
taskParentNames: test.parentNames,
|
|
668
|
-
taskType: 'suite',
|
|
669
|
-
testPath: test.testPath,
|
|
670
|
-
});
|
|
671
|
-
syncCurrentTask();
|
|
672
|
-
dispatchRunnerLifecycle('suite-start', test);
|
|
673
|
-
},
|
|
674
|
-
onTestSuiteResult: async (result) => {
|
|
675
|
-
removeTaskFromStack(result.testId);
|
|
676
|
-
dispatchRunnerLifecycle('suite-result', result);
|
|
677
|
-
},
|
|
678
|
-
onTestCaseStart: async (test) => {
|
|
679
|
-
taskStack.push({
|
|
680
|
-
taskId: test.testId,
|
|
681
|
-
taskName: test.name,
|
|
682
|
-
taskParentNames: test.parentNames,
|
|
683
|
-
taskType: 'case',
|
|
684
|
-
testPath: test.testPath,
|
|
685
|
-
});
|
|
686
|
-
syncCurrentTask();
|
|
687
|
-
dispatchRunnerLifecycle('case-start', test);
|
|
688
|
-
},
|
|
689
|
-
onTestCaseResult: async (result) => {
|
|
690
|
-
removeTaskFromStack(result.testId);
|
|
691
|
-
if (result.status === 'fail') {
|
|
692
|
-
failedTestsCount++;
|
|
693
|
-
}
|
|
694
|
-
send({
|
|
695
|
-
type: 'case-result',
|
|
696
|
-
payload: result,
|
|
697
|
-
});
|
|
698
|
-
},
|
|
699
|
-
getCountOfFailedTests: async () => failedTestsCount,
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
send({
|
|
703
|
-
type: 'file-start',
|
|
704
|
-
payload: {
|
|
705
|
-
testPath,
|
|
706
|
-
projectName: projectRuntime.name,
|
|
707
|
-
},
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
const unhandledErrors: Error[] = [];
|
|
711
|
-
activeUnhandledErrors = unhandledErrors;
|
|
712
|
-
|
|
713
|
-
try {
|
|
714
|
-
// Load setup files for this project after runtime is ready.
|
|
715
|
-
await loadSetupFiles();
|
|
716
|
-
|
|
717
|
-
// Record script URLs before loading the test file
|
|
718
|
-
const beforeScripts = getScriptUrls();
|
|
719
|
-
|
|
720
|
-
// Load the test file dynamically using this project's context
|
|
721
|
-
await currentTestContext.loadTest(key);
|
|
722
|
-
|
|
723
|
-
// Find the newly loaded chunk and preload its source map (for inline snapshots)
|
|
724
|
-
const afterScripts = getScriptUrls();
|
|
725
|
-
const chunkUrl = findNewScriptUrl(beforeScripts, afterScripts);
|
|
726
|
-
if (chunkUrl) {
|
|
727
|
-
await preloadTestFileSourceMap(chunkUrl);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
const result = await runtime.runner.runTests(
|
|
731
|
-
testPath,
|
|
732
|
-
runnerHooks,
|
|
733
|
-
runtime.api,
|
|
734
|
-
);
|
|
735
|
-
|
|
736
|
-
// The browser dispatches `unhandledrejection` in a task queued at the
|
|
737
|
-
// current task's microtask checkpoint, so a rejection leaked by a
|
|
738
|
-
// synchronous test is not observable yet when `runTests()` resolves.
|
|
739
|
-
// Yield two macrotasks: the first reaches the checkpoint that queues
|
|
740
|
-
// the event task, the second runs after that task regardless of how
|
|
741
|
-
// the browser orders the timer and event task sources.
|
|
742
|
-
for (let i = 0; i < 2; i++) {
|
|
743
|
-
await new Promise<void>((resolve) => {
|
|
744
|
-
setTimeout(resolve, 0);
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// An unhandled error/rejection that escaped the run fails the file even
|
|
749
|
-
// when every test passed.
|
|
750
|
-
if (unhandledErrors.length > 0) {
|
|
751
|
-
result.status = 'fail';
|
|
752
|
-
result.errors = [
|
|
753
|
-
...(result.errors ?? []),
|
|
754
|
-
...unhandledErrors.map((error) => ({
|
|
755
|
-
name: error.name,
|
|
756
|
-
message: error.message,
|
|
757
|
-
stack: error.stack,
|
|
758
|
-
})),
|
|
759
|
-
];
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// Collect coverage data from global __coverage__ object
|
|
763
|
-
if (globalThis.__coverage__) {
|
|
764
|
-
result.coverage = globalThis.__coverage__ as CoverageMapData;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
send({
|
|
768
|
-
type: 'file-complete',
|
|
769
|
-
payload: result,
|
|
770
|
-
});
|
|
771
|
-
} catch (_error) {
|
|
772
|
-
const error =
|
|
773
|
-
_error instanceof Error ? _error : new Error(String(_error));
|
|
774
|
-
send({
|
|
775
|
-
type: 'fatal',
|
|
776
|
-
payload: {
|
|
777
|
-
message: error.message,
|
|
778
|
-
stack: error.stack,
|
|
779
|
-
},
|
|
780
|
-
});
|
|
781
|
-
window.__RSTEST_DONE__ = true;
|
|
782
|
-
return;
|
|
783
|
-
} finally {
|
|
784
|
-
// Restore original console methods
|
|
785
|
-
restoreConsole();
|
|
786
|
-
activeUnhandledErrors = undefined;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
window.removeEventListener('error', onWindowError);
|
|
791
|
-
window.removeEventListener('unhandledrejection', onUnhandledRejection);
|
|
792
|
-
|
|
793
|
-
send({ type: 'complete' });
|
|
794
|
-
window.__RSTEST_DONE__ = true;
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
void run().catch((error) => {
|
|
798
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
799
|
-
send({
|
|
800
|
-
type: 'fatal',
|
|
801
|
-
payload: {
|
|
802
|
-
message: err.message,
|
|
803
|
-
stack: err.stack,
|
|
804
|
-
},
|
|
805
|
-
});
|
|
806
|
-
window.__RSTEST_DONE__ = true;
|
|
807
|
-
});
|