@rstest/browser 0.10.6 → 0.11.0
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/js/{854.c59f71483d.js → 328.37af068e21.js} +2712 -2786
- package/dist/browser-container/container-static/js/328.37af068e21.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.fedc31dff3.js → index.c8fe4ff310.js} +58 -56
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.js +1 -1
- package/dist/hostController.d.ts +17 -1
- package/dist/index.js +443 -365
- package/dist/protocol.d.ts +7 -1
- package/dist/providers/index.d.ts +3 -0
- package/package.json +6 -6
- package/src/client/entry.ts +25 -23
- package/src/client/formatConsole.ts +77 -0
- package/src/client/locator.ts +1 -5
- package/src/hostController.ts +671 -526
- package/src/protocol.ts +9 -5
- package/src/providers/index.ts +3 -0
- package/src/providers/playwright/runtime.ts +11 -6
- package/dist/browser-container/container-static/js/854.c59f71483d.js.LICENSE.txt +0 -1
- package/src/client/fakeTimersStub.ts +0 -91
- /package/dist/{626.js → 33.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ import sirv from "sirv";
|
|
|
9
9
|
import { WebSocketServer } from "ws";
|
|
10
10
|
import convert_source_map from "convert-source-map";
|
|
11
11
|
import { __webpack_require__ } from "./rslib-runtime.js";
|
|
12
|
-
import { DISPATCH_RPC_BRIDGE_NAME, DISPATCH_NAMESPACE_SNAPSHOT, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER } from "./
|
|
12
|
+
import { DISPATCH_RPC_BRIDGE_NAME, DISPATCH_NAMESPACE_SNAPSHOT, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER } from "./33.js";
|
|
13
13
|
__webpack_require__.add({
|
|
14
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
15
|
-
const pico = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
16
|
-
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
14
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/index.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
15
|
+
const pico = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/picomatch.js");
|
|
16
|
+
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js");
|
|
17
17
|
function picomatch(glob, options, returnState = false) {
|
|
18
18
|
if (options && (null === options.windows || void 0 === options.windows)) options = {
|
|
19
19
|
...options,
|
|
@@ -24,7 +24,7 @@ __webpack_require__.add({
|
|
|
24
24
|
Object.assign(picomatch, pico);
|
|
25
25
|
module.exports = picomatch;
|
|
26
26
|
},
|
|
27
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
27
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js" (module) {
|
|
28
28
|
const WIN_SLASH = '\\\\/';
|
|
29
29
|
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
|
|
30
30
|
const DEFAULT_MAX_EXTGLOB_RECURSION = 0;
|
|
@@ -187,9 +187,9 @@ __webpack_require__.add({
|
|
|
187
187
|
}
|
|
188
188
|
};
|
|
189
189
|
},
|
|
190
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
191
|
-
const constants = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
192
|
-
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
190
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/parse.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
191
|
+
const constants = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js");
|
|
192
|
+
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js");
|
|
193
193
|
const { MAX_LENGTH, POSIX_REGEX_SOURCE, REGEX_NON_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_BACKREF, REPLACEMENTS } = constants;
|
|
194
194
|
const expandRange = (args, options)=>{
|
|
195
195
|
if ('function' == typeof options.expandRange) return options.expandRange(...args, options);
|
|
@@ -333,7 +333,11 @@ __webpack_require__.add({
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
};
|
|
336
|
-
const
|
|
336
|
+
const buildCharClassStar = (chars)=>{
|
|
337
|
+
const source = 1 === chars.length ? utils.escapeRegex(chars[0]) : `[${chars.map((ch)=>utils.escapeRegex(ch)).join('')}]`;
|
|
338
|
+
return `${source}*`;
|
|
339
|
+
};
|
|
340
|
+
const getStarExtglobSequenceChars = (pattern)=>{
|
|
337
341
|
let index = 0;
|
|
338
342
|
const chars = [];
|
|
339
343
|
while(index < pattern.length){
|
|
@@ -347,8 +351,7 @@ __webpack_require__.add({
|
|
|
347
351
|
index += match.end + 1;
|
|
348
352
|
}
|
|
349
353
|
if (chars.length < 1) return;
|
|
350
|
-
|
|
351
|
-
return `${source}*`;
|
|
354
|
+
return chars;
|
|
352
355
|
};
|
|
353
356
|
const repeatedExtglobRecursion = (pattern)=>{
|
|
354
357
|
let depth = 0;
|
|
@@ -372,16 +375,34 @@ __webpack_require__.add({
|
|
|
372
375
|
risky: true
|
|
373
376
|
};
|
|
374
377
|
}
|
|
378
|
+
const safeChars = [];
|
|
379
|
+
let sawStarSequence = false;
|
|
380
|
+
let combinable = true;
|
|
375
381
|
for (const branch of branches){
|
|
376
|
-
const
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
382
|
+
const chars = getStarExtglobSequenceChars(branch);
|
|
383
|
+
if (chars) {
|
|
384
|
+
sawStarSequence = true;
|
|
385
|
+
safeChars.push(...chars);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const literal = normalizeSimpleBranch(branch);
|
|
389
|
+
if (literal && 1 === literal.length) {
|
|
390
|
+
safeChars.push(literal);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
combinable = false;
|
|
381
394
|
if (repeatedExtglobRecursion(branch) > max) return {
|
|
382
395
|
risky: true
|
|
383
396
|
};
|
|
384
397
|
}
|
|
398
|
+
if (sawStarSequence) return combinable ? {
|
|
399
|
+
risky: true,
|
|
400
|
+
safeOutput: buildCharClassStar([
|
|
401
|
+
...new Set(safeChars)
|
|
402
|
+
])
|
|
403
|
+
} : {
|
|
404
|
+
risky: true
|
|
405
|
+
};
|
|
385
406
|
return {
|
|
386
407
|
risky: false
|
|
387
408
|
};
|
|
@@ -1185,11 +1206,11 @@ __webpack_require__.add({
|
|
|
1185
1206
|
};
|
|
1186
1207
|
module.exports = parse;
|
|
1187
1208
|
},
|
|
1188
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
1189
|
-
const scan = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1190
|
-
const parse = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1191
|
-
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1192
|
-
const constants = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1209
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/picomatch.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1210
|
+
const scan = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/scan.js");
|
|
1211
|
+
const parse = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/parse.js");
|
|
1212
|
+
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js");
|
|
1213
|
+
const constants = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js");
|
|
1193
1214
|
const isObject = (val)=>val && 'object' == typeof val && !Array.isArray(val);
|
|
1194
1215
|
const picomatch = (glob, options, returnState = false)=>{
|
|
1195
1216
|
if (Array.isArray(glob)) {
|
|
@@ -1272,9 +1293,11 @@ __webpack_require__.add({
|
|
|
1272
1293
|
output
|
|
1273
1294
|
};
|
|
1274
1295
|
};
|
|
1275
|
-
picomatch.matchBase = (input, glob, options)=>{
|
|
1296
|
+
picomatch.matchBase = (input, glob, options, posix = options && options.windows)=>{
|
|
1276
1297
|
const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
|
|
1277
|
-
return regex.test(utils.basename(input
|
|
1298
|
+
return regex.test(utils.basename(input, {
|
|
1299
|
+
windows: posix
|
|
1300
|
+
}));
|
|
1278
1301
|
};
|
|
1279
1302
|
picomatch.isMatch = (str, patterns, options)=>picomatch(patterns, options)(str);
|
|
1280
1303
|
picomatch.parse = (pattern, options)=>{
|
|
@@ -1318,9 +1341,9 @@ __webpack_require__.add({
|
|
|
1318
1341
|
picomatch.constants = constants;
|
|
1319
1342
|
module.exports = picomatch;
|
|
1320
1343
|
},
|
|
1321
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
1322
|
-
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1323
|
-
const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1344
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/scan.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1345
|
+
const utils = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js");
|
|
1346
|
+
const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js");
|
|
1324
1347
|
const isPathSeparator = (code)=>code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
|
1325
1348
|
const depth = (token)=>{
|
|
1326
1349
|
if (true !== token.isPrefix) token.depth = token.isGlobstar ? 1 / 0 : 1;
|
|
@@ -1586,8 +1609,8 @@ __webpack_require__.add({
|
|
|
1586
1609
|
};
|
|
1587
1610
|
module.exports = scan;
|
|
1588
1611
|
},
|
|
1589
|
-
"../../node_modules/.pnpm/picomatch@4.0.
|
|
1590
|
-
const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
1612
|
+
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js" (__unused_rspack_module, exports, __webpack_require__) {
|
|
1613
|
+
const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js");
|
|
1591
1614
|
exports.isObject = (val)=>null !== val && 'object' == typeof val && !Array.isArray(val);
|
|
1592
1615
|
exports.hasRegexChars = (str)=>REGEX_SPECIAL_CHARS.test(str);
|
|
1593
1616
|
exports.isRegexChar = (str)=>1 === str.length && exports.hasRegexChars(str);
|
|
@@ -2522,6 +2545,7 @@ const wrapPage = (page)=>{
|
|
|
2522
2545
|
if ('popup' === event) return void originalOn(event, (popup)=>{
|
|
2523
2546
|
listener(wrapPage(popup));
|
|
2524
2547
|
});
|
|
2548
|
+
if ('crash' === event || 'close' === event) return void originalOn(event, listener);
|
|
2525
2549
|
originalOn(event, listener);
|
|
2526
2550
|
};
|
|
2527
2551
|
return withAsyncDispose(page);
|
|
@@ -2810,12 +2834,11 @@ const planWatchRerun = ({ projectEntries, previousTestFiles, affectedTestFiles }
|
|
|
2810
2834
|
affectedTestFiles: matchedAffectedFiles
|
|
2811
2835
|
};
|
|
2812
2836
|
};
|
|
2813
|
-
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.
|
|
2837
|
+
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/index.js");
|
|
2814
2838
|
var picomatch_default = /*#__PURE__*/ __webpack_require__.n(picomatch);
|
|
2815
2839
|
const { createRsbuild: createRsbuild, rspack: rspack } = rsbuild;
|
|
2816
2840
|
const hostController_dirname = dirname(fileURLToPath(import.meta.url));
|
|
2817
2841
|
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
2818
|
-
const PER_FILE_TIMEOUT_BUFFER_MS = 30000;
|
|
2819
2842
|
let nextBrowserFilePid = 1000000000;
|
|
2820
2843
|
const serializeForInlineScript = (value)=>JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
2821
2844
|
const getBrowserProviderOptions = (project)=>{
|
|
@@ -2991,9 +3014,10 @@ const createBrowserLazyCompilationConfig = (setupFiles)=>{
|
|
|
2991
3014
|
}
|
|
2992
3015
|
};
|
|
2993
3016
|
};
|
|
2994
|
-
const
|
|
3017
|
+
const shouldEnableBrowserHmr = (isWatchMode, isHeadless)=>isWatchMode && !isHeadless;
|
|
3018
|
+
const createBrowserRsbuildDevConfig = (enableHmr)=>({
|
|
2995
3019
|
writeToDisk: isDebug(),
|
|
2996
|
-
hmr:
|
|
3020
|
+
hmr: enableHmr,
|
|
2997
3021
|
client: {
|
|
2998
3022
|
logLevel: 'error'
|
|
2999
3023
|
}
|
|
@@ -3183,7 +3207,7 @@ const ensureConsistentBrowserLaunchOptions = (projects)=>{
|
|
|
3183
3207
|
const firstOptions = getBrowserLaunchOptions(firstProject);
|
|
3184
3208
|
for (const project of projects.slice(1)){
|
|
3185
3209
|
const options = getBrowserLaunchOptions(project);
|
|
3186
|
-
if (options.provider !== firstOptions.provider || options.browser !== firstOptions.browser || options.headless !== firstOptions.headless ||
|
|
3210
|
+
if (options.provider !== firstOptions.provider || options.browser !== firstOptions.browser || options.headless !== firstOptions.headless || !isDeepStrictEqual(options.providerOptions, firstOptions.providerOptions)) throw new Error(`Browser launch config mismatch between projects "${firstProject.name}" and "${project.name}". All browser-enabled projects in one run must share provider/browser/headless/providerOptions.`);
|
|
3187
3211
|
}
|
|
3188
3212
|
return firstOptions;
|
|
3189
3213
|
};
|
|
@@ -3230,7 +3254,14 @@ const resolveContainerDist = ()=>{
|
|
|
3230
3254
|
throw new Error(`Browser container build not found at ${distPath}. Please run "pnpm --filter @rstest/browser build".`);
|
|
3231
3255
|
};
|
|
3232
3256
|
const toSafeVarName = (name)=>name.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
3233
|
-
const
|
|
3257
|
+
const toContextKey = (filePath, projectRootPosix)=>{
|
|
3258
|
+
const posixPath = normalize(filePath);
|
|
3259
|
+
const withinRoot = posixPath === projectRootPosix || posixPath.startsWith(`${projectRootPosix}/`);
|
|
3260
|
+
if (!withinRoot) return posixPath;
|
|
3261
|
+
const rel = posixPath.slice(projectRootPosix.length);
|
|
3262
|
+
return rel.startsWith('/') ? `.${rel}` : `./${rel}`;
|
|
3263
|
+
};
|
|
3264
|
+
const generateManifestModule = ({ manifestPath, entries, isWatchMode })=>{
|
|
3234
3265
|
const manifestDirPosix = normalize(dirname(manifestPath));
|
|
3235
3266
|
const toRelativeImport = (filePath)=>{
|
|
3236
3267
|
const posixPath = normalize(filePath);
|
|
@@ -3263,18 +3294,30 @@ const generateManifestModule = ({ manifestPath, entries })=>{
|
|
|
3263
3294
|
lines.push('};');
|
|
3264
3295
|
lines.push('');
|
|
3265
3296
|
lines.push('// Test context for each project');
|
|
3266
|
-
for (const { project } of entries){
|
|
3297
|
+
for (const { project, testFiles } of entries){
|
|
3267
3298
|
const varName = `context_${toSafeVarName(project.environmentName)}`;
|
|
3268
3299
|
const projectRootPosix = normalize(project.rootPath);
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3300
|
+
if (isWatchMode) {
|
|
3301
|
+
const includeRegExp = globPatternsToRegExp(project.normalizedConfig.include);
|
|
3302
|
+
const excludeRegExp = createBrowserContextExcludeRegExp(project.normalizedConfig.exclude.patterns, projectRootPosix);
|
|
3303
|
+
lines.push(`const ${varName} = import.meta.webpackContext(${JSON.stringify(projectRootPosix)}, {`);
|
|
3304
|
+
lines.push(' recursive: true,');
|
|
3305
|
+
lines.push(` regExp: ${includeRegExp.toString()},`);
|
|
3306
|
+
if (excludeRegExp) lines.push(` exclude: ${excludeRegExp.toString()},`);
|
|
3307
|
+
lines.push(" mode: 'lazy',");
|
|
3308
|
+
lines.push('});');
|
|
3309
|
+
} else {
|
|
3310
|
+
lines.push(`const ${varName}_modules = {`);
|
|
3311
|
+
for (const filePath of testFiles){
|
|
3312
|
+
const key = toContextKey(filePath, projectRootPosix);
|
|
3313
|
+
const importPath = toRelativeImport(filePath);
|
|
3314
|
+
lines.push(` ${JSON.stringify(key)}: () => import(${JSON.stringify(importPath)}),`);
|
|
3315
|
+
}
|
|
3316
|
+
lines.push('};');
|
|
3317
|
+
lines.push(`const ${varName} = Object.assign((key) => ${varName}_modules[key](), {`);
|
|
3318
|
+
lines.push(` keys: () => Object.keys(${varName}_modules),`);
|
|
3319
|
+
lines.push('});');
|
|
3320
|
+
}
|
|
3278
3321
|
lines.push('');
|
|
3279
3322
|
}
|
|
3280
3323
|
lines.push('export const projectTestContexts = {');
|
|
@@ -3308,13 +3351,21 @@ const htmlTemplate = `<!DOCTYPE html>
|
|
|
3308
3351
|
</html>
|
|
3309
3352
|
`;
|
|
3310
3353
|
const VIRTUAL_MANIFEST_FILENAME = 'virtual-manifest.ts';
|
|
3354
|
+
const closeAllProjectServers = (servers)=>Promise.allSettled([
|
|
3355
|
+
...servers
|
|
3356
|
+
].map((server)=>server.devServer.close()));
|
|
3357
|
+
const copyProxyResponseHeaders = (response, res)=>{
|
|
3358
|
+
res.statusCode = response.status;
|
|
3359
|
+
response.headers.forEach((value, key)=>{
|
|
3360
|
+
if ('content-length' === key.toLowerCase()) return;
|
|
3361
|
+
res.setHeader(key, value);
|
|
3362
|
+
});
|
|
3363
|
+
};
|
|
3311
3364
|
const destroyBrowserRuntime = async (runtime)=>{
|
|
3312
3365
|
try {
|
|
3313
3366
|
await runtime.browser?.close?.();
|
|
3314
3367
|
} catch {}
|
|
3315
|
-
|
|
3316
|
-
await runtime.devServer?.close?.();
|
|
3317
|
-
} catch {}
|
|
3368
|
+
await closeAllProjectServers(runtime.projectServers.values());
|
|
3318
3369
|
try {
|
|
3319
3370
|
runtime.wss?.close();
|
|
3320
3371
|
} catch {}
|
|
@@ -3348,10 +3399,7 @@ const registerWatchCleanup = ()=>{
|
|
|
3348
3399
|
});
|
|
3349
3400
|
watchContext.cleanupRegistered = true;
|
|
3350
3401
|
};
|
|
3351
|
-
const createBrowserRuntime = async ({ context,
|
|
3352
|
-
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
3353
|
-
[manifestPath]: manifestSource
|
|
3354
|
-
});
|
|
3402
|
+
const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless })=>{
|
|
3355
3403
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
3356
3404
|
let injectedContainerHtml = null;
|
|
3357
3405
|
let serializedOptions = 'null';
|
|
@@ -3361,156 +3409,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3361
3409
|
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3362
3410
|
};
|
|
3363
3411
|
const browserProjects = getBrowserProjects(context);
|
|
3364
|
-
const projectByEnvironmentName = new Map(browserProjects.map((project)=>[
|
|
3365
|
-
project.environmentName,
|
|
3366
|
-
project
|
|
3367
|
-
]));
|
|
3368
|
-
const userPlugins = browserProjects.flatMap((project)=>project.normalizedConfig.plugins || []);
|
|
3369
3412
|
const browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3370
3413
|
const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/internal/browser-runtime'));
|
|
3371
|
-
const
|
|
3372
|
-
'@rstest/browser-manifest': manifestPath,
|
|
3414
|
+
const staticRstestAliases = {
|
|
3373
3415
|
'@rstest/core': resolveBrowserFile('client/public.ts'),
|
|
3374
3416
|
'@rstest/browser': resolveBrowserFile('browser.ts'),
|
|
3375
|
-
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
3376
|
-
'@sinonjs/fake-timers': resolveBrowserFile('client/fakeTimersStub.ts')
|
|
3417
|
+
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
3377
3418
|
};
|
|
3378
|
-
const
|
|
3379
|
-
callerName: 'rstest-browser',
|
|
3380
|
-
rsbuildConfig: {
|
|
3381
|
-
root: context.rootPath,
|
|
3382
|
-
mode: 'development',
|
|
3383
|
-
plugins: userPlugins,
|
|
3384
|
-
server: {
|
|
3385
|
-
printUrls: false,
|
|
3386
|
-
port: browserLaunchOptions.port ?? 4000,
|
|
3387
|
-
strictPort: browserLaunchOptions.strictPort
|
|
3388
|
-
},
|
|
3389
|
-
dev: createBrowserRsbuildDevConfig(isWatchMode),
|
|
3390
|
-
environments: {
|
|
3391
|
-
...Object.fromEntries(browserProjects.map((project)=>[
|
|
3392
|
-
project.environmentName,
|
|
3393
|
-
{}
|
|
3394
|
-
]))
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
});
|
|
3398
|
-
rsbuildInstance.addPlugins([
|
|
3399
|
-
{
|
|
3400
|
-
name: 'rstest:browser-user-config',
|
|
3401
|
-
setup (api) {
|
|
3402
|
-
api.expose?.('rstest:browser', {
|
|
3403
|
-
registerDispatchHandler: (namespace, handler)=>{
|
|
3404
|
-
dispatchHandlers.set(namespace, handler);
|
|
3405
|
-
}
|
|
3406
|
-
});
|
|
3407
|
-
api.modifyEnvironmentConfig({
|
|
3408
|
-
handler: (config, { mergeEnvironmentConfig, name })=>{
|
|
3409
|
-
const project = projectByEnvironmentName.get(name);
|
|
3410
|
-
if (!project) return config;
|
|
3411
|
-
const userRsbuildConfig = project.normalizedConfig;
|
|
3412
|
-
const buildCache = resolveProjectBuildCache({
|
|
3413
|
-
context,
|
|
3414
|
-
project
|
|
3415
|
-
});
|
|
3416
|
-
const setupFiles = Object.values(getSetupFiles(project.normalizedConfig.setupFiles, project.rootPath));
|
|
3417
|
-
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(RSTEST_ENV_SYMBOL_KEY)})]`;
|
|
3418
|
-
const merged = mergeEnvironmentConfig(config, {
|
|
3419
|
-
...userRsbuildConfig,
|
|
3420
|
-
performance: buildCache ? {
|
|
3421
|
-
...userRsbuildConfig.performance,
|
|
3422
|
-
buildCache
|
|
3423
|
-
} : userRsbuildConfig.performance
|
|
3424
|
-
}, {
|
|
3425
|
-
resolve: {
|
|
3426
|
-
alias: rstestInternalAliases
|
|
3427
|
-
},
|
|
3428
|
-
source: {
|
|
3429
|
-
define: {
|
|
3430
|
-
'process.env': rstestEnvDefine,
|
|
3431
|
-
'import.meta.env': rstestEnvDefine
|
|
3432
|
-
}
|
|
3433
|
-
},
|
|
3434
|
-
output: {
|
|
3435
|
-
target: 'web',
|
|
3436
|
-
sourceMap: {
|
|
3437
|
-
js: 'source-map'
|
|
3438
|
-
}
|
|
3439
|
-
},
|
|
3440
|
-
tools: {
|
|
3441
|
-
rspack: (rspackConfig)=>{
|
|
3442
|
-
rspackConfig.mode = 'development';
|
|
3443
|
-
rspackConfig.lazyCompilation = createBrowserLazyCompilationConfig(setupFiles);
|
|
3444
|
-
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
3445
|
-
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
3446
|
-
applyDefaultWatchOptions(rspackConfig, isWatchMode);
|
|
3447
|
-
const browserRuntimeDir = dirname(browserRuntimePath);
|
|
3448
|
-
rspackConfig.module = rspackConfig.module || {};
|
|
3449
|
-
rspackConfig.module.rules = rspackConfig.module.rules || [];
|
|
3450
|
-
rspackConfig.module.rules.unshift({
|
|
3451
|
-
test: /\.js$/,
|
|
3452
|
-
include: browserRuntimeDir,
|
|
3453
|
-
extractSourceMap: true
|
|
3454
|
-
});
|
|
3455
|
-
if (isDebug()) logger.log(`[rstest:browser] extractSourceMap rule added for: ${browserRuntimeDir}`);
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
});
|
|
3459
|
-
merged.source = merged.source || {};
|
|
3460
|
-
merged.source.entry = {
|
|
3461
|
-
runner: resolveBrowserFile('client/entry.ts')
|
|
3462
|
-
};
|
|
3463
|
-
return merged;
|
|
3464
|
-
},
|
|
3465
|
-
order: 'post'
|
|
3466
|
-
});
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
]);
|
|
3470
|
-
if (isWatchMode && onTriggerRerun) rsbuildInstance.addPlugins([
|
|
3471
|
-
{
|
|
3472
|
-
name: 'rstest:browser-watch',
|
|
3473
|
-
setup (api) {
|
|
3474
|
-
api.onBeforeDevCompile(()=>{
|
|
3475
|
-
if (!watchContext.hooksEnabled) return;
|
|
3476
|
-
logger.log(color.cyan('\nFile changed, re-running tests...\n'));
|
|
3477
|
-
});
|
|
3478
|
-
api.onAfterDevCompile(async ({ stats })=>{
|
|
3479
|
-
if (stats) {
|
|
3480
|
-
const projectEntries = await collectProjectEntries(context);
|
|
3481
|
-
const entryTestFiles = new Set(collectWatchTestFiles(projectEntries).map((file)=>file.testPath));
|
|
3482
|
-
const statsJson = stats.toJson({
|
|
3483
|
-
all: true
|
|
3484
|
-
});
|
|
3485
|
-
const affected = getAffectedTestFiles(statsJson.chunks, entryTestFiles);
|
|
3486
|
-
watchContext.affectedTestFiles = affected;
|
|
3487
|
-
if (affected.length > 0) logger.debug(`[Watch] Affected test files: ${affected.join(', ')}`);
|
|
3488
|
-
}
|
|
3489
|
-
if (!watchContext.hooksEnabled) return;
|
|
3490
|
-
await onTriggerRerun();
|
|
3491
|
-
});
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
]);
|
|
3495
|
-
const coverage = browserProjects.find((project)=>project.normalizedConfig.coverage?.enabled)?.normalizedConfig.coverage;
|
|
3496
|
-
if (coverage?.enabled && 'list' !== context.command) {
|
|
3497
|
-
const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
|
|
3498
|
-
rsbuildInstance.addPlugins([
|
|
3499
|
-
pluginCoverage(coverage)
|
|
3500
|
-
]);
|
|
3501
|
-
}
|
|
3502
|
-
const devServer = await rsbuildInstance.createDevServer({
|
|
3503
|
-
getPortSilently: true
|
|
3504
|
-
});
|
|
3505
|
-
if (isDebug()) await rsbuildInstance.inspectConfig({
|
|
3506
|
-
writeToDisk: true,
|
|
3507
|
-
extraConfigs: {
|
|
3508
|
-
rstest: {
|
|
3509
|
-
...context.normalizedConfig,
|
|
3510
|
-
projects: browserProjects.map((p)=>p.normalizedConfig)
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
});
|
|
3419
|
+
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(RSTEST_ENV_SYMBOL_KEY)})]`;
|
|
3514
3420
|
const serveContainer = containerDistPath ? sirv(containerDistPath, {
|
|
3515
3421
|
dev: false,
|
|
3516
3422
|
single: 'index.html'
|
|
@@ -3524,11 +3430,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3524
3430
|
if (!response.ok) return false;
|
|
3525
3431
|
let html = await response.text();
|
|
3526
3432
|
html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3527
|
-
res
|
|
3528
|
-
response.headers.forEach((value, key)=>{
|
|
3529
|
-
if ('content-length' === key.toLowerCase()) return;
|
|
3530
|
-
res.setHeader(key, value);
|
|
3531
|
-
});
|
|
3433
|
+
copyProxyResponseHeaders(response, res);
|
|
3532
3434
|
res.setHeader('Content-Type', 'text/html');
|
|
3533
3435
|
res.end(html);
|
|
3534
3436
|
return true;
|
|
@@ -3544,11 +3446,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3544
3446
|
const response = await fetch(target);
|
|
3545
3447
|
if (!response.ok) return false;
|
|
3546
3448
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
3547
|
-
res
|
|
3548
|
-
response.headers.forEach((value, key)=>{
|
|
3549
|
-
if ('content-length' === key.toLowerCase()) return;
|
|
3550
|
-
res.setHeader(key, value);
|
|
3551
|
-
});
|
|
3449
|
+
copyProxyResponseHeaders(response, res);
|
|
3552
3450
|
res.end(buffer);
|
|
3553
3451
|
return true;
|
|
3554
3452
|
} catch (error) {
|
|
@@ -3556,59 +3454,241 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3556
3454
|
return false;
|
|
3557
3455
|
}
|
|
3558
3456
|
};
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3457
|
+
const entryByEnvironmentName = new Map(projectEntries.map((entry)=>[
|
|
3458
|
+
entry.project.environmentName,
|
|
3459
|
+
entry
|
|
3460
|
+
]));
|
|
3461
|
+
const buildProjectServer = async (project, isContainerServer)=>{
|
|
3462
|
+
const manifestPath = join(tempDir, toSafeVarName(project.environmentName), VIRTUAL_MANIFEST_FILENAME);
|
|
3463
|
+
const entry = entryByEnvironmentName.get(project.environmentName);
|
|
3464
|
+
const manifestSource = generateManifestModule({
|
|
3465
|
+
manifestPath,
|
|
3466
|
+
entries: [
|
|
3467
|
+
{
|
|
3468
|
+
project,
|
|
3469
|
+
testFiles: entry?.testFiles ?? [],
|
|
3470
|
+
setupFiles: entry?.setupFiles ?? []
|
|
3471
|
+
}
|
|
3472
|
+
],
|
|
3473
|
+
isWatchMode
|
|
3474
|
+
});
|
|
3475
|
+
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
3476
|
+
[manifestPath]: manifestSource
|
|
3477
|
+
});
|
|
3478
|
+
const rstestInternalAliases = {
|
|
3479
|
+
'@rstest/browser-manifest': manifestPath,
|
|
3480
|
+
...staticRstestAliases
|
|
3481
|
+
};
|
|
3482
|
+
const isHeadless = forceHeadless || project.normalizedConfig.browser.headless;
|
|
3483
|
+
const enableHmr = shouldEnableBrowserHmr(isWatchMode, isHeadless);
|
|
3484
|
+
const rsbuildInstance = await createRsbuild({
|
|
3485
|
+
callerName: 'rstest-browser',
|
|
3486
|
+
rsbuildConfig: {
|
|
3487
|
+
root: context.rootPath,
|
|
3488
|
+
mode: 'development',
|
|
3489
|
+
plugins: project.normalizedConfig.plugins || [],
|
|
3490
|
+
server: {
|
|
3491
|
+
printUrls: false,
|
|
3492
|
+
port: project.normalizedConfig.browser.port ?? (isContainerServer ? 4000 : 0),
|
|
3493
|
+
strictPort: project.normalizedConfig.browser.strictPort
|
|
3494
|
+
},
|
|
3495
|
+
dev: createBrowserRsbuildDevConfig(enableHmr),
|
|
3496
|
+
environments: {
|
|
3497
|
+
[project.environmentName]: {}
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
});
|
|
3501
|
+
rsbuildInstance.addPlugins([
|
|
3502
|
+
{
|
|
3503
|
+
name: 'rstest:browser-user-config',
|
|
3504
|
+
setup (api) {
|
|
3505
|
+
api.expose?.('rstest:browser', {
|
|
3506
|
+
registerDispatchHandler: (namespace, handler)=>{
|
|
3507
|
+
dispatchHandlers.set(namespace, handler);
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
api.modifyEnvironmentConfig({
|
|
3511
|
+
handler: (config, { mergeEnvironmentConfig, name })=>{
|
|
3512
|
+
if (name !== project.environmentName) return config;
|
|
3513
|
+
const userRsbuildConfig = project.normalizedConfig;
|
|
3514
|
+
const buildCache = resolveProjectBuildCache({
|
|
3515
|
+
context,
|
|
3516
|
+
project
|
|
3517
|
+
});
|
|
3518
|
+
const setupFiles = Object.values(getSetupFiles(project.normalizedConfig.setupFiles, project.rootPath));
|
|
3519
|
+
const merged = mergeEnvironmentConfig(config, {
|
|
3520
|
+
...userRsbuildConfig,
|
|
3521
|
+
performance: buildCache ? {
|
|
3522
|
+
...userRsbuildConfig.performance,
|
|
3523
|
+
buildCache
|
|
3524
|
+
} : userRsbuildConfig.performance
|
|
3525
|
+
}, {
|
|
3526
|
+
resolve: {
|
|
3527
|
+
alias: rstestInternalAliases
|
|
3528
|
+
},
|
|
3529
|
+
source: {
|
|
3530
|
+
define: {
|
|
3531
|
+
'process.env': rstestEnvDefine,
|
|
3532
|
+
'import.meta.env': rstestEnvDefine
|
|
3533
|
+
}
|
|
3534
|
+
},
|
|
3535
|
+
output: {
|
|
3536
|
+
target: 'web',
|
|
3537
|
+
sourceMap: {
|
|
3538
|
+
js: 'source-map'
|
|
3539
|
+
}
|
|
3540
|
+
},
|
|
3541
|
+
tools: {
|
|
3542
|
+
rspack: (rspackConfig)=>{
|
|
3543
|
+
rspackConfig.mode = 'development';
|
|
3544
|
+
rspackConfig.lazyCompilation = enableHmr ? createBrowserLazyCompilationConfig(setupFiles) : false;
|
|
3545
|
+
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
3546
|
+
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
3547
|
+
applyDefaultWatchOptions(rspackConfig, isWatchMode);
|
|
3548
|
+
const browserRuntimeDir = dirname(browserRuntimePath);
|
|
3549
|
+
rspackConfig.module = rspackConfig.module || {};
|
|
3550
|
+
rspackConfig.module.rules = rspackConfig.module.rules || [];
|
|
3551
|
+
rspackConfig.module.rules.unshift({
|
|
3552
|
+
test: /\.js$/,
|
|
3553
|
+
include: browserRuntimeDir,
|
|
3554
|
+
extractSourceMap: true
|
|
3555
|
+
});
|
|
3556
|
+
if (isDebug()) logger.log(`[rstest:browser] extractSourceMap rule added for: ${browserRuntimeDir}`);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
merged.source = merged.source || {};
|
|
3561
|
+
merged.source.entry = {
|
|
3562
|
+
runner: resolveBrowserFile('client/entry.ts')
|
|
3563
|
+
};
|
|
3564
|
+
return merged;
|
|
3565
|
+
},
|
|
3566
|
+
order: 'post'
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
]);
|
|
3571
|
+
if (isWatchMode && onTriggerRerun) rsbuildInstance.addPlugins([
|
|
3572
|
+
{
|
|
3573
|
+
name: 'rstest:browser-watch',
|
|
3574
|
+
setup (api) {
|
|
3575
|
+
api.onBeforeDevCompile(()=>{
|
|
3576
|
+
if (!watchContext.hooksEnabled) return;
|
|
3577
|
+
logger.log(color.cyan('\nFile changed, re-running tests...\n'));
|
|
3578
|
+
});
|
|
3579
|
+
api.onAfterDevCompile(async ({ stats })=>{
|
|
3580
|
+
if (stats) {
|
|
3581
|
+
const allProjectEntries = await collectProjectEntries(context);
|
|
3582
|
+
const entryTestFiles = new Set(collectWatchTestFiles(allProjectEntries).map((file)=>file.testPath));
|
|
3583
|
+
const statsJson = stats.toJson({
|
|
3584
|
+
all: true
|
|
3585
|
+
});
|
|
3586
|
+
const affected = getAffectedTestFiles(statsJson.chunks, entryTestFiles);
|
|
3587
|
+
watchContext.affectedTestFiles = affected;
|
|
3588
|
+
if (affected.length > 0) logger.debug(`[Watch] Affected test files: ${affected.join(', ')}`);
|
|
3589
|
+
}
|
|
3590
|
+
if (!watchContext.hooksEnabled) return;
|
|
3591
|
+
await onTriggerRerun();
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
]);
|
|
3596
|
+
const coverage = project.normalizedConfig.coverage;
|
|
3597
|
+
if (coverage?.enabled && 'list' !== context.command) {
|
|
3598
|
+
const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
|
|
3599
|
+
rsbuildInstance.addPlugins([
|
|
3600
|
+
pluginCoverage(coverage)
|
|
3601
|
+
]);
|
|
3602
|
+
}
|
|
3603
|
+
const devServer = await rsbuildInstance.createDevServer({
|
|
3604
|
+
getPortSilently: true
|
|
3605
|
+
});
|
|
3606
|
+
if (isDebug()) await rsbuildInstance.inspectConfig({
|
|
3607
|
+
writeToDisk: true,
|
|
3608
|
+
extraConfigs: {
|
|
3609
|
+
rstest: {
|
|
3610
|
+
...context.normalizedConfig,
|
|
3611
|
+
projects: [
|
|
3612
|
+
project.normalizedConfig
|
|
3613
|
+
]
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
});
|
|
3617
|
+
devServer.middlewares.use(async (req, res, next)=>{
|
|
3618
|
+
if (!req.url) return void next();
|
|
3619
|
+
const url = new URL(req.url, 'http://localhost');
|
|
3620
|
+
if ('/__open-in-editor' === url.pathname) {
|
|
3621
|
+
const file = url.searchParams.get('file');
|
|
3622
|
+
if (!file) {
|
|
3623
|
+
res.statusCode = 400;
|
|
3624
|
+
res.end('Missing file');
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
try {
|
|
3628
|
+
await open_editor([
|
|
3629
|
+
{
|
|
3630
|
+
file
|
|
3631
|
+
}
|
|
3632
|
+
]);
|
|
3633
|
+
res.statusCode = 204;
|
|
3634
|
+
res.end();
|
|
3635
|
+
} catch (error) {
|
|
3636
|
+
logger.debug(`[Browser UI] Failed to open editor: ${String(error)}`);
|
|
3637
|
+
res.statusCode = 500;
|
|
3638
|
+
res.end('Failed to open editor');
|
|
3639
|
+
}
|
|
3567
3640
|
return;
|
|
3568
3641
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3642
|
+
if (isContainerServer) {
|
|
3643
|
+
if ('/' === url.pathname) {
|
|
3644
|
+
if (await respondWithDevServerHtml(url, res)) return;
|
|
3645
|
+
const html = injectedContainerHtml || containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
|
|
3646
|
+
if (html) {
|
|
3647
|
+
res.setHeader('Content-Type', 'text/html');
|
|
3648
|
+
res.end(html);
|
|
3649
|
+
return;
|
|
3573
3650
|
}
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3651
|
+
res.statusCode = 502;
|
|
3652
|
+
res.end('Container UI is not available.');
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
if (url.pathname.startsWith('/container-static/')) {
|
|
3656
|
+
if (await proxyDevServerAsset(req, res)) return;
|
|
3657
|
+
if (serveContainer) return void serveContainer(req, res, next);
|
|
3658
|
+
res.statusCode = 502;
|
|
3659
|
+
res.end('Container assets are not available.');
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3581
3662
|
}
|
|
3582
|
-
|
|
3583
|
-
}
|
|
3584
|
-
if ('/' === url.pathname) {
|
|
3585
|
-
if (await respondWithDevServerHtml(url, res)) return;
|
|
3586
|
-
const html = injectedContainerHtml || containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
|
|
3587
|
-
if (html) {
|
|
3663
|
+
if ('/runner.html' === url.pathname) {
|
|
3588
3664
|
res.setHeader('Content-Type', 'text/html');
|
|
3589
|
-
res.end(
|
|
3665
|
+
res.end(htmlTemplate);
|
|
3590
3666
|
return;
|
|
3591
3667
|
}
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3668
|
+
next();
|
|
3669
|
+
});
|
|
3670
|
+
const { port: listenPort } = await devServer.listen();
|
|
3671
|
+
const port = resolveListenPort(listenPort, devServer.httpServer);
|
|
3672
|
+
return {
|
|
3673
|
+
projectName: project.name,
|
|
3674
|
+
environmentName: project.environmentName,
|
|
3675
|
+
rsbuildInstance,
|
|
3676
|
+
devServer,
|
|
3677
|
+
port,
|
|
3678
|
+
manifestPath
|
|
3679
|
+
};
|
|
3680
|
+
};
|
|
3681
|
+
const projectServers = new Map();
|
|
3682
|
+
try {
|
|
3683
|
+
for (const [index, project] of browserProjects.entries()){
|
|
3684
|
+
const server = await buildProjectServer(project, 0 === index);
|
|
3685
|
+
projectServers.set(server.projectName, server);
|
|
3607
3686
|
}
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3687
|
+
} catch (error) {
|
|
3688
|
+
await closeAllProjectServers(projectServers.values());
|
|
3689
|
+
throw error;
|
|
3690
|
+
}
|
|
3691
|
+
const containerServer = projectServers.get(browserProjects[0].name);
|
|
3612
3692
|
const wss = new WebSocketServer({
|
|
3613
3693
|
port: 0
|
|
3614
3694
|
});
|
|
@@ -3627,22 +3707,19 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3627
3707
|
providerOptions: browserLaunchOptions.providerOptions
|
|
3628
3708
|
});
|
|
3629
3709
|
return {
|
|
3630
|
-
|
|
3631
|
-
|
|
3710
|
+
projectServers,
|
|
3711
|
+
containerServer,
|
|
3632
3712
|
browser: runtime.browser,
|
|
3633
3713
|
browserLaunchOptions,
|
|
3634
|
-
port,
|
|
3635
3714
|
wsPort,
|
|
3636
|
-
manifestPath,
|
|
3637
3715
|
tempDir,
|
|
3638
|
-
manifestPlugin: virtualManifestPlugin,
|
|
3639
3716
|
setContainerOptions,
|
|
3640
3717
|
dispatchHandlers,
|
|
3641
3718
|
wss
|
|
3642
3719
|
};
|
|
3643
3720
|
} catch (error) {
|
|
3644
3721
|
wss.close();
|
|
3645
|
-
await
|
|
3722
|
+
await closeAllProjectServers(projectServers.values());
|
|
3646
3723
|
throw error;
|
|
3647
3724
|
}
|
|
3648
3725
|
};
|
|
@@ -3813,11 +3890,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
3813
3890
|
const enableCliShortcuts = isWatchMode && isBrowserWatchCliShortcutsEnabled();
|
|
3814
3891
|
const browserTempOutputRoot = context.normalizedConfig.output.distPath.root;
|
|
3815
3892
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, browserTempOutputRoot, 'browser', 'watch') : join(context.rootPath, browserTempOutputRoot, 'browser', Date.now().toString());
|
|
3816
|
-
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
3817
|
-
const manifestSource = generateManifestModule({
|
|
3818
|
-
manifestPath,
|
|
3819
|
-
entries: projectEntries
|
|
3820
|
-
});
|
|
3821
3893
|
if (isWatchMode) watchContext.lastTestFiles = collectWatchTestFiles(projectEntries);
|
|
3822
3894
|
let runtime = isWatchMode ? watchContext.runtime : null;
|
|
3823
3895
|
let triggerRerun;
|
|
@@ -3825,8 +3897,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3825
3897
|
try {
|
|
3826
3898
|
runtime = await createBrowserRuntime({
|
|
3827
3899
|
context,
|
|
3828
|
-
|
|
3829
|
-
manifestSource,
|
|
3900
|
+
projectEntries,
|
|
3830
3901
|
tempDir,
|
|
3831
3902
|
isWatchMode,
|
|
3832
3903
|
onTriggerRerun: isWatchMode ? async ()=>{
|
|
@@ -3851,7 +3922,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3851
3922
|
});
|
|
3852
3923
|
}
|
|
3853
3924
|
}
|
|
3854
|
-
const { browser, browserLaunchOptions,
|
|
3925
|
+
const { browser, browserLaunchOptions, wsPort, wss } = runtime;
|
|
3855
3926
|
const buildTime = Date.now() - buildStart;
|
|
3856
3927
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
3857
3928
|
testPath: normalize(testPath),
|
|
@@ -3865,13 +3936,20 @@ const runBrowserController = async (context, options)=>{
|
|
|
3865
3936
|
viewport: project.normalizedConfig.browser.viewport
|
|
3866
3937
|
}));
|
|
3867
3938
|
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? DEFAULT_TEST_TIMEOUT));
|
|
3939
|
+
const projectRunnerUrls = Object.fromEntries([
|
|
3940
|
+
...runtime.projectServers
|
|
3941
|
+
].map(([name, server])=>[
|
|
3942
|
+
name,
|
|
3943
|
+
`http://localhost:${server.port}`
|
|
3944
|
+
]));
|
|
3868
3945
|
const hostOptions = {
|
|
3869
3946
|
rootPath: normalize(context.rootPath),
|
|
3870
3947
|
projects: projectRuntimeConfigs,
|
|
3871
3948
|
snapshot: {
|
|
3872
3949
|
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
3873
3950
|
},
|
|
3874
|
-
runnerUrl: `http://localhost:${port}`,
|
|
3951
|
+
runnerUrl: `http://localhost:${runtime.containerServer.port}`,
|
|
3952
|
+
projectRunnerUrls,
|
|
3875
3953
|
wsPort,
|
|
3876
3954
|
debug: isDebug(),
|
|
3877
3955
|
rpcTimeout: maxTestTimeoutForRpc
|
|
@@ -4190,11 +4268,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
4190
4268
|
const donePromise = new Promise((resolve)=>{
|
|
4191
4269
|
resolveDone = resolve;
|
|
4192
4270
|
});
|
|
4193
|
-
const
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4271
|
+
const crashDeferred = createDeferredPromise();
|
|
4272
|
+
const onPageDead = (reason)=>{
|
|
4273
|
+
if (settled || run.cancelled || !runLifecycle.isTokenActive(run.token)) return;
|
|
4274
|
+
settled = true;
|
|
4275
|
+
crashDeferred.resolve(reason);
|
|
4276
|
+
};
|
|
4196
4277
|
try {
|
|
4197
4278
|
page = await browserContext.newPage();
|
|
4279
|
+
page.on('crash', ()=>onPageDead(`Browser page crashed while running ${file.testPath}.`));
|
|
4280
|
+
page.on('close', ()=>onPageDead(`Browser page closed unexpectedly while running ${file.testPath}.`));
|
|
4198
4281
|
const session = sessionRegistry.register({
|
|
4199
4282
|
testFile: file.testPath,
|
|
4200
4283
|
projectName: file.projectName,
|
|
@@ -4241,21 +4324,27 @@ const runBrowserController = async (context, options)=>{
|
|
|
4241
4324
|
};
|
|
4242
4325
|
const serializedOptions = serializeForInlineScript(inlineOptions);
|
|
4243
4326
|
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
4244
|
-
|
|
4327
|
+
const projectServer = runtime.projectServers.get(file.projectName);
|
|
4328
|
+
if (!projectServer) throw new Error(`No browser dev server for project "${file.projectName}" (test file: ${file.testPath}).`);
|
|
4329
|
+
await page.goto(`http://localhost:${projectServer.port}/runner.html`, {
|
|
4245
4330
|
waitUntil: 'load'
|
|
4246
4331
|
});
|
|
4247
|
-
const timeoutPromise = new Promise((resolve)=>{
|
|
4248
|
-
timeoutId = setTimeout(()=>resolve('timeout'), perFileTimeoutMs);
|
|
4249
|
-
});
|
|
4250
4332
|
const state = await Promise.race([
|
|
4251
|
-
donePromise.then(()=>
|
|
4252
|
-
|
|
4253
|
-
|
|
4333
|
+
donePromise.then(()=>({
|
|
4334
|
+
type: 'done'
|
|
4335
|
+
})),
|
|
4336
|
+
crashDeferred.promise.then((reason)=>({
|
|
4337
|
+
type: 'crash',
|
|
4338
|
+
reason
|
|
4339
|
+
})),
|
|
4340
|
+
run.cancelSignal.then(()=>({
|
|
4341
|
+
type: 'cancelled'
|
|
4342
|
+
}))
|
|
4254
4343
|
]);
|
|
4255
|
-
if ('cancelled' === state) return;
|
|
4256
|
-
if ('
|
|
4344
|
+
if ('cancelled' === state.type) return;
|
|
4345
|
+
if ('crash' === state.type && runLifecycle.isTokenActive(run.token) && !run.cancelled) {
|
|
4257
4346
|
await handleFatal({
|
|
4258
|
-
message:
|
|
4347
|
+
message: state.reason
|
|
4259
4348
|
});
|
|
4260
4349
|
await cancelRun(run, false);
|
|
4261
4350
|
}
|
|
@@ -4269,7 +4358,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4269
4358
|
await cancelRun(run, false);
|
|
4270
4359
|
}
|
|
4271
4360
|
} finally{
|
|
4272
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
4273
4361
|
if (page) try {
|
|
4274
4362
|
await page.close();
|
|
4275
4363
|
} catch {}
|
|
@@ -4512,10 +4600,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4512
4600
|
if (!fileInfo) throw new Error(`Unknown browser test file: ${JSON.stringify(testFile)}`);
|
|
4513
4601
|
return fileInfo;
|
|
4514
4602
|
};
|
|
4515
|
-
const getHeadedPerFileTimeoutMs = (file)=>{
|
|
4516
|
-
const projectRuntime = projectRuntimeConfigs.find((project)=>project.name === file.projectName);
|
|
4517
|
-
return (projectRuntime?.runtimeConfig.testTimeout ?? maxTestTimeoutForRpc) + PER_FILE_TIMEOUT_BUFFER_MS;
|
|
4518
|
-
};
|
|
4519
4603
|
let containerContext;
|
|
4520
4604
|
let containerPage;
|
|
4521
4605
|
let isNewPage = false;
|
|
@@ -4585,26 +4669,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
4585
4669
|
pendingHeadedReloads.delete(testPath);
|
|
4586
4670
|
pending.deferred.resolve();
|
|
4587
4671
|
};
|
|
4588
|
-
const
|
|
4589
|
-
const timeoutMs = getHeadedPerFileTimeoutMs(file);
|
|
4590
|
-
let timeoutId;
|
|
4672
|
+
const reloadTestFileAndWait = async (file, testNamePattern)=>{
|
|
4591
4673
|
let reloadAck;
|
|
4592
4674
|
try {
|
|
4593
4675
|
reloadAck = await rpcManager.reloadTestFile(file.testPath, testNamePattern);
|
|
4594
|
-
|
|
4595
|
-
await Promise.race([
|
|
4596
|
-
completionPromise,
|
|
4597
|
-
new Promise((_, reject)=>{
|
|
4598
|
-
timeoutId = setTimeout(()=>{
|
|
4599
|
-
reject(new Error(`Headed test execution timeout after ${timeoutMs / 1000}s for ${file.testPath}.`));
|
|
4600
|
-
}, timeoutMs);
|
|
4601
|
-
})
|
|
4602
|
-
]);
|
|
4676
|
+
await registerPendingHeadedReload(file.testPath, reloadAck.runId);
|
|
4603
4677
|
} catch (error) {
|
|
4604
4678
|
if (reloadAck?.runId) rejectPendingHeadedReload(file.testPath, toError(error), reloadAck.runId);
|
|
4605
4679
|
throw error;
|
|
4606
|
-
} finally{
|
|
4607
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
4608
4680
|
}
|
|
4609
4681
|
};
|
|
4610
4682
|
const createRpcMethods = ()=>({
|
|
@@ -4661,14 +4733,15 @@ const runBrowserController = async (context, options)=>{
|
|
|
4661
4733
|
}
|
|
4662
4734
|
if (isNewPage) {
|
|
4663
4735
|
const pagePath = '/';
|
|
4664
|
-
|
|
4736
|
+
const containerPort = runtime.containerServer.port;
|
|
4737
|
+
await containerPage.goto(`http://localhost:${containerPort}${pagePath}`, {
|
|
4665
4738
|
waitUntil: 'load'
|
|
4666
4739
|
});
|
|
4667
|
-
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${
|
|
4740
|
+
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${containerPort}${pagePath}\n`));
|
|
4668
4741
|
}
|
|
4669
4742
|
enqueueHeadedReload = async (file, testNamePattern)=>headedReloadQueue.enqueue(async ()=>{
|
|
4670
4743
|
if (fatalError) return;
|
|
4671
|
-
await
|
|
4744
|
+
await reloadTestFileAndWait(file, testNamePattern);
|
|
4672
4745
|
});
|
|
4673
4746
|
let testTime = 0;
|
|
4674
4747
|
if (currentTestFiles.length > 0) {
|
|
@@ -4789,18 +4862,12 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4789
4862
|
close: async ()=>{}
|
|
4790
4863
|
};
|
|
4791
4864
|
const tempDir = join(context.rootPath, context.normalizedConfig.output.distPath.root, 'browser', `list-${Date.now()}`);
|
|
4792
|
-
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
4793
|
-
const manifestSource = generateManifestModule({
|
|
4794
|
-
manifestPath,
|
|
4795
|
-
entries: projectEntries
|
|
4796
|
-
});
|
|
4797
4865
|
const browserProjects = getBrowserProjects(context);
|
|
4798
4866
|
let runtime;
|
|
4799
4867
|
try {
|
|
4800
4868
|
runtime = await createBrowserRuntime({
|
|
4801
4869
|
context,
|
|
4802
|
-
|
|
4803
|
-
manifestSource,
|
|
4870
|
+
projectEntries,
|
|
4804
4871
|
tempDir,
|
|
4805
4872
|
isWatchMode: false,
|
|
4806
4873
|
containerDistPath: void 0,
|
|
@@ -4814,7 +4881,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4814
4881
|
logger.error(color.red(`Failed to initialize browser provider runtime (${providers.join(', ')}).`), error);
|
|
4815
4882
|
throw error;
|
|
4816
4883
|
}
|
|
4817
|
-
const { browser, browserLaunchOptions
|
|
4884
|
+
const { browser, browserLaunchOptions } = runtime;
|
|
4818
4885
|
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
4819
4886
|
name: project.name,
|
|
4820
4887
|
environmentName: project.environmentName,
|
|
@@ -4834,70 +4901,81 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4834
4901
|
rpcTimeout: maxTestTimeoutForRpc
|
|
4835
4902
|
};
|
|
4836
4903
|
runtime.setContainerOptions(hostOptions);
|
|
4837
|
-
const collectResults = [];
|
|
4838
|
-
let fatalError = null;
|
|
4839
|
-
let collectCompleted = false;
|
|
4840
|
-
let resolveCollect;
|
|
4841
|
-
const collectPromise = new Promise((resolve)=>{
|
|
4842
|
-
resolveCollect = resolve;
|
|
4843
|
-
});
|
|
4844
4904
|
const browserContext = await browser.newContext({
|
|
4845
4905
|
providerOptions: browserLaunchOptions.providerOptions,
|
|
4846
4906
|
viewport: null
|
|
4847
4907
|
});
|
|
4848
|
-
const
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4908
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
4909
|
+
const collectFromServer = async (server)=>{
|
|
4910
|
+
const results = [];
|
|
4911
|
+
let error = null;
|
|
4912
|
+
let collectCompleted = false;
|
|
4913
|
+
let resolveCollect;
|
|
4914
|
+
const collectPromise = new Promise((resolve)=>{
|
|
4915
|
+
resolveCollect = resolve;
|
|
4916
|
+
});
|
|
4917
|
+
const page = await browserContext.newPage();
|
|
4918
|
+
await page.exposeFunction(DISPATCH_MESSAGE_TYPE, (message)=>{
|
|
4919
|
+
switch(message.type){
|
|
4920
|
+
case 'collect-result':
|
|
4921
|
+
{
|
|
4922
|
+
const payload = message.payload;
|
|
4923
|
+
results.push({
|
|
4924
|
+
testPath: payload.testPath,
|
|
4925
|
+
project: payload.project,
|
|
4926
|
+
tests: payload.tests
|
|
4927
|
+
});
|
|
4928
|
+
break;
|
|
4929
|
+
}
|
|
4930
|
+
case 'collect-complete':
|
|
4931
|
+
collectCompleted = true;
|
|
4870
4932
|
resolveCollect?.();
|
|
4871
4933
|
break;
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4934
|
+
case 'fatal':
|
|
4935
|
+
{
|
|
4936
|
+
const payload = message.payload;
|
|
4937
|
+
error = new Error(payload.message);
|
|
4938
|
+
error.stack = payload.stack;
|
|
4939
|
+
resolveCollect?.();
|
|
4940
|
+
break;
|
|
4941
|
+
}
|
|
4942
|
+
case 'ready':
|
|
4943
|
+
case 'log':
|
|
4944
|
+
break;
|
|
4945
|
+
default:
|
|
4946
|
+
logger.debug(`[List] Unexpected message: ${message.type}`);
|
|
4947
|
+
}
|
|
4948
|
+
});
|
|
4949
|
+
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
4950
|
+
await page.goto(`http://localhost:${server.port}/runner.html`, {
|
|
4951
|
+
waitUntil: 'load'
|
|
4952
|
+
});
|
|
4953
|
+
const timeoutMs = 30000;
|
|
4954
|
+
let timeoutId;
|
|
4955
|
+
const timeoutPromise = new Promise((resolve)=>{
|
|
4956
|
+
timeoutId = setTimeout(()=>{
|
|
4957
|
+
if (!collectCompleted) logger.warn(color.yellow(`[List] Browser test collection timed out after ${timeoutMs}ms`));
|
|
4958
|
+
resolve();
|
|
4959
|
+
}, timeoutMs);
|
|
4960
|
+
});
|
|
4961
|
+
await Promise.race([
|
|
4962
|
+
collectPromise,
|
|
4963
|
+
timeoutPromise
|
|
4964
|
+
]);
|
|
4965
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
4966
|
+
await page.close().catch(()=>{});
|
|
4967
|
+
return {
|
|
4968
|
+
results,
|
|
4969
|
+
error
|
|
4970
|
+
};
|
|
4971
|
+
};
|
|
4972
|
+
const collected = await Promise.all([
|
|
4973
|
+
...runtime.projectServers.values()
|
|
4974
|
+
].map((server)=>collectFromServer(server)));
|
|
4975
|
+
const collectResults = collected.flatMap((entry)=>entry.results);
|
|
4976
|
+
const fatalError = collected.find((entry)=>entry.error)?.error ?? null;
|
|
4898
4977
|
const cleanup = async ()=>{
|
|
4899
4978
|
try {
|
|
4900
|
-
await page.close();
|
|
4901
4979
|
await browserContext.close();
|
|
4902
4980
|
} catch {}
|
|
4903
4981
|
await destroyBrowserRuntime(runtime);
|