@rstest/browser 0.10.5 → 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 +18 -1
- package/dist/index.js +513 -378
- package/dist/protocol.d.ts +7 -1
- package/dist/providers/index.d.ts +3 -0
- package/package.json +7 -7
- 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 +842 -548
- 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
|
}
|
|
@@ -3002,7 +3026,8 @@ const globToRegexp = (glob)=>{
|
|
|
3002
3026
|
const regex = picomatch_default().makeRe(glob, {
|
|
3003
3027
|
fastpaths: false,
|
|
3004
3028
|
noglobstar: false,
|
|
3005
|
-
bash: false
|
|
3029
|
+
bash: false,
|
|
3030
|
+
dot: true
|
|
3006
3031
|
});
|
|
3007
3032
|
if (!regex) throw new Error(`Invalid glob pattern: ${glob}`);
|
|
3008
3033
|
if (!glob.startsWith('./')) return regex;
|
|
@@ -3022,20 +3047,76 @@ const globPatternsToRegExp = (patterns)=>{
|
|
|
3022
3047
|
});
|
|
3023
3048
|
return new RegExp(`(?:${regexParts.join('|')})$`);
|
|
3024
3049
|
};
|
|
3025
|
-
const
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3050
|
+
const REGEXP_SPECIAL_CHARACTERS = /[|\\{}()[\]^$+*?.]/g;
|
|
3051
|
+
const PATH_SEPARATOR_SOURCE = String.raw`[\\/]`;
|
|
3052
|
+
const WINDOWS_ABSOLUTE_PATH_SOURCE = String.raw`[A-Za-z]:[\\/]`;
|
|
3053
|
+
const escapeRegExp = (value)=>value.replace(REGEXP_SPECIAL_CHARACTERS, '\\$&');
|
|
3054
|
+
const normalizePathForRegExp = (value)=>normalize(value).replaceAll('\\', '/');
|
|
3055
|
+
const normalizeExcludePatternForRegExp = (value)=>value.startsWith('./') ? `./${normalizePathForRegExp(value.substring(2))}` : normalizePathForRegExp(value);
|
|
3056
|
+
const isAbsolutePatternForRegExp = (value)=>value.startsWith('/') || /^[A-Za-z]:\//.test(value);
|
|
3057
|
+
const isEscapedRegExpCharacter = (source, index)=>{
|
|
3058
|
+
let backslashCount = 0;
|
|
3059
|
+
for(let current = index - 1; current >= 0 && '\\' === source[current]; current--)backslashCount++;
|
|
3060
|
+
return backslashCount % 2 === 1;
|
|
3061
|
+
};
|
|
3062
|
+
const replacePathSeparatorsInRegExpSource = (source)=>{
|
|
3063
|
+
let result = '';
|
|
3064
|
+
let inCharacterClass = false;
|
|
3065
|
+
for(let index = 0; index < source.length; index++){
|
|
3066
|
+
const character = source[index];
|
|
3067
|
+
const isEscaped = isEscapedRegExpCharacter(source, index);
|
|
3068
|
+
if ('[' === character && !isEscaped) inCharacterClass = true;
|
|
3069
|
+
if (!inCharacterClass && '\\' === character && '/' === source[index + 1]) {
|
|
3070
|
+
result += PATH_SEPARATOR_SOURCE;
|
|
3071
|
+
index++;
|
|
3072
|
+
continue;
|
|
3035
3073
|
}
|
|
3074
|
+
result += character;
|
|
3075
|
+
if (']' === character && !isEscaped) inCharacterClass = false;
|
|
3076
|
+
}
|
|
3077
|
+
return result;
|
|
3078
|
+
};
|
|
3079
|
+
const createRelativeContextExcludeSource = (source, normalizedPattern)=>{
|
|
3080
|
+
if (normalizedPattern.startsWith('./')) return source;
|
|
3081
|
+
return normalizedPattern.startsWith('**/') ? `(?:(?:${source})|\\.(?:${source}))` : `(?:(?:${source})|\\.${PATH_SEPARATOR_SOURCE}(?:${source}))`;
|
|
3082
|
+
};
|
|
3083
|
+
const createProjectAbsoluteExcludeSource = (source, normalizedPattern)=>normalizedPattern.startsWith('./') || normalizedPattern.startsWith('**/') ? source : `${PATH_SEPARATOR_SOURCE}(?:${source})`;
|
|
3084
|
+
const excludePatternsToRegExpSources = (patterns)=>{
|
|
3085
|
+
const sources = patterns.map((pattern)=>{
|
|
3086
|
+
const normalizedPattern = normalizeExcludePatternForRegExp(pattern);
|
|
3087
|
+
const regex = globToRegexp(normalizedPattern);
|
|
3088
|
+
let source = regex.source;
|
|
3089
|
+
if (source.startsWith('^')) source = source.substring(1);
|
|
3090
|
+
if (source.endsWith('$')) source = source.substring(0, source.length - 1);
|
|
3091
|
+
source = replacePathSeparatorsInRegExpSource(source);
|
|
3092
|
+
const isAbsolute = isAbsolutePatternForRegExp(normalizedPattern);
|
|
3093
|
+
const absolute = normalizedPattern.startsWith('./') ? source.substring(2) : source;
|
|
3094
|
+
return {
|
|
3095
|
+
relative: isAbsolute ? source : createRelativeContextExcludeSource(source, normalizedPattern),
|
|
3096
|
+
absolute: isAbsolute ? absolute : createProjectAbsoluteExcludeSource(absolute, normalizedPattern),
|
|
3097
|
+
isAbsolute
|
|
3098
|
+
};
|
|
3099
|
+
});
|
|
3100
|
+
if (0 === sources.length) return null;
|
|
3101
|
+
return sources;
|
|
3102
|
+
};
|
|
3103
|
+
const createBrowserContextExcludeRegExp = (patterns, projectRoot)=>{
|
|
3104
|
+
const excludeSources = excludePatternsToRegExpSources(patterns);
|
|
3105
|
+
if (!excludeSources) return null;
|
|
3106
|
+
const normalizedProjectRoot = normalizePathForRegExp(projectRoot).replace(/[\\/]$/, '');
|
|
3107
|
+
const projectRootSource = normalizedProjectRoot.split('/').map(escapeRegExp).join(PATH_SEPARATOR_SOURCE);
|
|
3108
|
+
const relativeExcludeSources = excludeSources.filter((source)=>!source.isAbsolute);
|
|
3109
|
+
const absoluteExcludeSources = excludeSources.filter((source)=>source.isAbsolute);
|
|
3110
|
+
const sourceBranches = [];
|
|
3111
|
+
if (relativeExcludeSources.length > 0) {
|
|
3112
|
+
const relativePatternSource = `(?:${relativeExcludeSources.map((source)=>source.relative).join('|')})`;
|
|
3113
|
+
const absolutePatternSource = `(?:${relativeExcludeSources.map((source)=>source.absolute).join('|')})`;
|
|
3114
|
+
const relativeSource = `(?:${relativePatternSource})`;
|
|
3115
|
+
const absoluteSource = normalizedProjectRoot ? `${projectRootSource}(?=${PATH_SEPARATOR_SOURCE})(?:${absolutePatternSource})` : `(?:${absolutePatternSource})`;
|
|
3116
|
+
sourceBranches.push(`(?!${WINDOWS_ABSOLUTE_PATH_SOURCE}|${PATH_SEPARATOR_SOURCE})${relativeSource}`, absoluteSource);
|
|
3036
3117
|
}
|
|
3037
|
-
if (0
|
|
3038
|
-
return new RegExp(
|
|
3118
|
+
if (absoluteExcludeSources.length > 0) sourceBranches.push(`(?:${absoluteExcludeSources.map((source)=>source.relative).join('|')})`);
|
|
3119
|
+
return new RegExp(`^(?:${sourceBranches.join('|')})$`);
|
|
3039
3120
|
};
|
|
3040
3121
|
const findTestFileInModules = (modules, entryTestFiles)=>{
|
|
3041
3122
|
if (!modules) return null;
|
|
@@ -3126,7 +3207,7 @@ const ensureConsistentBrowserLaunchOptions = (projects)=>{
|
|
|
3126
3207
|
const firstOptions = getBrowserLaunchOptions(firstProject);
|
|
3127
3208
|
for (const project of projects.slice(1)){
|
|
3128
3209
|
const options = getBrowserLaunchOptions(project);
|
|
3129
|
-
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.`);
|
|
3130
3211
|
}
|
|
3131
3212
|
return firstOptions;
|
|
3132
3213
|
};
|
|
@@ -3173,7 +3254,14 @@ const resolveContainerDist = ()=>{
|
|
|
3173
3254
|
throw new Error(`Browser container build not found at ${distPath}. Please run "pnpm --filter @rstest/browser build".`);
|
|
3174
3255
|
};
|
|
3175
3256
|
const toSafeVarName = (name)=>name.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
3176
|
-
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 })=>{
|
|
3177
3265
|
const manifestDirPosix = normalize(dirname(manifestPath));
|
|
3178
3266
|
const toRelativeImport = (filePath)=>{
|
|
3179
3267
|
const posixPath = normalize(filePath);
|
|
@@ -3206,18 +3294,30 @@ const generateManifestModule = ({ manifestPath, entries })=>{
|
|
|
3206
3294
|
lines.push('};');
|
|
3207
3295
|
lines.push('');
|
|
3208
3296
|
lines.push('// Test context for each project');
|
|
3209
|
-
for (const { project } of entries){
|
|
3297
|
+
for (const { project, testFiles } of entries){
|
|
3210
3298
|
const varName = `context_${toSafeVarName(project.environmentName)}`;
|
|
3211
3299
|
const projectRootPosix = normalize(project.rootPath);
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
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
|
+
}
|
|
3221
3321
|
lines.push('');
|
|
3222
3322
|
}
|
|
3223
3323
|
lines.push('export const projectTestContexts = {');
|
|
@@ -3251,13 +3351,21 @@ const htmlTemplate = `<!DOCTYPE html>
|
|
|
3251
3351
|
</html>
|
|
3252
3352
|
`;
|
|
3253
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
|
+
};
|
|
3254
3364
|
const destroyBrowserRuntime = async (runtime)=>{
|
|
3255
3365
|
try {
|
|
3256
3366
|
await runtime.browser?.close?.();
|
|
3257
3367
|
} catch {}
|
|
3258
|
-
|
|
3259
|
-
await runtime.devServer?.close?.();
|
|
3260
|
-
} catch {}
|
|
3368
|
+
await closeAllProjectServers(runtime.projectServers.values());
|
|
3261
3369
|
try {
|
|
3262
3370
|
runtime.wss?.close();
|
|
3263
3371
|
} catch {}
|
|
@@ -3291,10 +3399,7 @@ const registerWatchCleanup = ()=>{
|
|
|
3291
3399
|
});
|
|
3292
3400
|
watchContext.cleanupRegistered = true;
|
|
3293
3401
|
};
|
|
3294
|
-
const createBrowserRuntime = async ({ context,
|
|
3295
|
-
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
3296
|
-
[manifestPath]: manifestSource
|
|
3297
|
-
});
|
|
3402
|
+
const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless })=>{
|
|
3298
3403
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
3299
3404
|
let injectedContainerHtml = null;
|
|
3300
3405
|
let serializedOptions = 'null';
|
|
@@ -3304,156 +3409,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3304
3409
|
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3305
3410
|
};
|
|
3306
3411
|
const browserProjects = getBrowserProjects(context);
|
|
3307
|
-
const projectByEnvironmentName = new Map(browserProjects.map((project)=>[
|
|
3308
|
-
project.environmentName,
|
|
3309
|
-
project
|
|
3310
|
-
]));
|
|
3311
|
-
const userPlugins = browserProjects.flatMap((project)=>project.normalizedConfig.plugins || []);
|
|
3312
3412
|
const browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3313
3413
|
const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/internal/browser-runtime'));
|
|
3314
|
-
const
|
|
3315
|
-
'@rstest/browser-manifest': manifestPath,
|
|
3414
|
+
const staticRstestAliases = {
|
|
3316
3415
|
'@rstest/core': resolveBrowserFile('client/public.ts'),
|
|
3317
3416
|
'@rstest/browser': resolveBrowserFile('browser.ts'),
|
|
3318
|
-
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
3319
|
-
'@sinonjs/fake-timers': resolveBrowserFile('client/fakeTimersStub.ts')
|
|
3417
|
+
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
3320
3418
|
};
|
|
3321
|
-
const
|
|
3322
|
-
callerName: 'rstest-browser',
|
|
3323
|
-
rsbuildConfig: {
|
|
3324
|
-
root: context.rootPath,
|
|
3325
|
-
mode: 'development',
|
|
3326
|
-
plugins: userPlugins,
|
|
3327
|
-
server: {
|
|
3328
|
-
printUrls: false,
|
|
3329
|
-
port: browserLaunchOptions.port ?? 4000,
|
|
3330
|
-
strictPort: browserLaunchOptions.strictPort
|
|
3331
|
-
},
|
|
3332
|
-
dev: createBrowserRsbuildDevConfig(isWatchMode),
|
|
3333
|
-
environments: {
|
|
3334
|
-
...Object.fromEntries(browserProjects.map((project)=>[
|
|
3335
|
-
project.environmentName,
|
|
3336
|
-
{}
|
|
3337
|
-
]))
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
});
|
|
3341
|
-
rsbuildInstance.addPlugins([
|
|
3342
|
-
{
|
|
3343
|
-
name: 'rstest:browser-user-config',
|
|
3344
|
-
setup (api) {
|
|
3345
|
-
api.expose?.('rstest:browser', {
|
|
3346
|
-
registerDispatchHandler: (namespace, handler)=>{
|
|
3347
|
-
dispatchHandlers.set(namespace, handler);
|
|
3348
|
-
}
|
|
3349
|
-
});
|
|
3350
|
-
api.modifyEnvironmentConfig({
|
|
3351
|
-
handler: (config, { mergeEnvironmentConfig, name })=>{
|
|
3352
|
-
const project = projectByEnvironmentName.get(name);
|
|
3353
|
-
if (!project) return config;
|
|
3354
|
-
const userRsbuildConfig = project.normalizedConfig;
|
|
3355
|
-
const buildCache = resolveProjectBuildCache({
|
|
3356
|
-
context,
|
|
3357
|
-
project
|
|
3358
|
-
});
|
|
3359
|
-
const setupFiles = Object.values(getSetupFiles(project.normalizedConfig.setupFiles, project.rootPath));
|
|
3360
|
-
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(RSTEST_ENV_SYMBOL_KEY)})]`;
|
|
3361
|
-
const merged = mergeEnvironmentConfig(config, {
|
|
3362
|
-
...userRsbuildConfig,
|
|
3363
|
-
performance: buildCache ? {
|
|
3364
|
-
...userRsbuildConfig.performance,
|
|
3365
|
-
buildCache
|
|
3366
|
-
} : userRsbuildConfig.performance
|
|
3367
|
-
}, {
|
|
3368
|
-
resolve: {
|
|
3369
|
-
alias: rstestInternalAliases
|
|
3370
|
-
},
|
|
3371
|
-
source: {
|
|
3372
|
-
define: {
|
|
3373
|
-
'process.env': rstestEnvDefine,
|
|
3374
|
-
'import.meta.env': rstestEnvDefine
|
|
3375
|
-
}
|
|
3376
|
-
},
|
|
3377
|
-
output: {
|
|
3378
|
-
target: 'web',
|
|
3379
|
-
sourceMap: {
|
|
3380
|
-
js: 'source-map'
|
|
3381
|
-
}
|
|
3382
|
-
},
|
|
3383
|
-
tools: {
|
|
3384
|
-
rspack: (rspackConfig)=>{
|
|
3385
|
-
rspackConfig.mode = 'development';
|
|
3386
|
-
rspackConfig.lazyCompilation = createBrowserLazyCompilationConfig(setupFiles);
|
|
3387
|
-
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
3388
|
-
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
3389
|
-
applyDefaultWatchOptions(rspackConfig, isWatchMode);
|
|
3390
|
-
const browserRuntimeDir = dirname(browserRuntimePath);
|
|
3391
|
-
rspackConfig.module = rspackConfig.module || {};
|
|
3392
|
-
rspackConfig.module.rules = rspackConfig.module.rules || [];
|
|
3393
|
-
rspackConfig.module.rules.unshift({
|
|
3394
|
-
test: /\.js$/,
|
|
3395
|
-
include: browserRuntimeDir,
|
|
3396
|
-
extractSourceMap: true
|
|
3397
|
-
});
|
|
3398
|
-
if (isDebug()) logger.log(`[rstest:browser] extractSourceMap rule added for: ${browserRuntimeDir}`);
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
});
|
|
3402
|
-
merged.source = merged.source || {};
|
|
3403
|
-
merged.source.entry = {
|
|
3404
|
-
runner: resolveBrowserFile('client/entry.ts')
|
|
3405
|
-
};
|
|
3406
|
-
return merged;
|
|
3407
|
-
},
|
|
3408
|
-
order: 'post'
|
|
3409
|
-
});
|
|
3410
|
-
}
|
|
3411
|
-
}
|
|
3412
|
-
]);
|
|
3413
|
-
if (isWatchMode && onTriggerRerun) rsbuildInstance.addPlugins([
|
|
3414
|
-
{
|
|
3415
|
-
name: 'rstest:browser-watch',
|
|
3416
|
-
setup (api) {
|
|
3417
|
-
api.onBeforeDevCompile(()=>{
|
|
3418
|
-
if (!watchContext.hooksEnabled) return;
|
|
3419
|
-
logger.log(color.cyan('\nFile changed, re-running tests...\n'));
|
|
3420
|
-
});
|
|
3421
|
-
api.onAfterDevCompile(async ({ stats })=>{
|
|
3422
|
-
if (stats) {
|
|
3423
|
-
const projectEntries = await collectProjectEntries(context);
|
|
3424
|
-
const entryTestFiles = new Set(collectWatchTestFiles(projectEntries).map((file)=>file.testPath));
|
|
3425
|
-
const statsJson = stats.toJson({
|
|
3426
|
-
all: true
|
|
3427
|
-
});
|
|
3428
|
-
const affected = getAffectedTestFiles(statsJson.chunks, entryTestFiles);
|
|
3429
|
-
watchContext.affectedTestFiles = affected;
|
|
3430
|
-
if (affected.length > 0) logger.debug(`[Watch] Affected test files: ${affected.join(', ')}`);
|
|
3431
|
-
}
|
|
3432
|
-
if (!watchContext.hooksEnabled) return;
|
|
3433
|
-
await onTriggerRerun();
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
]);
|
|
3438
|
-
const coverage = browserProjects.find((project)=>project.normalizedConfig.coverage?.enabled)?.normalizedConfig.coverage;
|
|
3439
|
-
if (coverage?.enabled && 'list' !== context.command) {
|
|
3440
|
-
const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
|
|
3441
|
-
rsbuildInstance.addPlugins([
|
|
3442
|
-
pluginCoverage(coverage)
|
|
3443
|
-
]);
|
|
3444
|
-
}
|
|
3445
|
-
const devServer = await rsbuildInstance.createDevServer({
|
|
3446
|
-
getPortSilently: true
|
|
3447
|
-
});
|
|
3448
|
-
if (isDebug()) await rsbuildInstance.inspectConfig({
|
|
3449
|
-
writeToDisk: true,
|
|
3450
|
-
extraConfigs: {
|
|
3451
|
-
rstest: {
|
|
3452
|
-
...context.normalizedConfig,
|
|
3453
|
-
projects: browserProjects.map((p)=>p.normalizedConfig)
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
});
|
|
3419
|
+
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(RSTEST_ENV_SYMBOL_KEY)})]`;
|
|
3457
3420
|
const serveContainer = containerDistPath ? sirv(containerDistPath, {
|
|
3458
3421
|
dev: false,
|
|
3459
3422
|
single: 'index.html'
|
|
@@ -3467,11 +3430,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3467
3430
|
if (!response.ok) return false;
|
|
3468
3431
|
let html = await response.text();
|
|
3469
3432
|
html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3470
|
-
res
|
|
3471
|
-
response.headers.forEach((value, key)=>{
|
|
3472
|
-
if ('content-length' === key.toLowerCase()) return;
|
|
3473
|
-
res.setHeader(key, value);
|
|
3474
|
-
});
|
|
3433
|
+
copyProxyResponseHeaders(response, res);
|
|
3475
3434
|
res.setHeader('Content-Type', 'text/html');
|
|
3476
3435
|
res.end(html);
|
|
3477
3436
|
return true;
|
|
@@ -3487,11 +3446,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3487
3446
|
const response = await fetch(target);
|
|
3488
3447
|
if (!response.ok) return false;
|
|
3489
3448
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
3490
|
-
res
|
|
3491
|
-
response.headers.forEach((value, key)=>{
|
|
3492
|
-
if ('content-length' === key.toLowerCase()) return;
|
|
3493
|
-
res.setHeader(key, value);
|
|
3494
|
-
});
|
|
3449
|
+
copyProxyResponseHeaders(response, res);
|
|
3495
3450
|
res.end(buffer);
|
|
3496
3451
|
return true;
|
|
3497
3452
|
} catch (error) {
|
|
@@ -3499,59 +3454,241 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3499
3454
|
return false;
|
|
3500
3455
|
}
|
|
3501
3456
|
};
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
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
|
+
}
|
|
3510
3640
|
return;
|
|
3511
3641
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
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;
|
|
3516
3650
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
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
|
+
}
|
|
3524
3662
|
}
|
|
3525
|
-
|
|
3526
|
-
}
|
|
3527
|
-
if ('/' === url.pathname) {
|
|
3528
|
-
if (await respondWithDevServerHtml(url, res)) return;
|
|
3529
|
-
const html = injectedContainerHtml || containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
|
|
3530
|
-
if (html) {
|
|
3663
|
+
if ('/runner.html' === url.pathname) {
|
|
3531
3664
|
res.setHeader('Content-Type', 'text/html');
|
|
3532
|
-
res.end(
|
|
3665
|
+
res.end(htmlTemplate);
|
|
3533
3666
|
return;
|
|
3534
3667
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
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);
|
|
3550
3686
|
}
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3687
|
+
} catch (error) {
|
|
3688
|
+
await closeAllProjectServers(projectServers.values());
|
|
3689
|
+
throw error;
|
|
3690
|
+
}
|
|
3691
|
+
const containerServer = projectServers.get(browserProjects[0].name);
|
|
3555
3692
|
const wss = new WebSocketServer({
|
|
3556
3693
|
port: 0
|
|
3557
3694
|
});
|
|
@@ -3570,22 +3707,19 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3570
3707
|
providerOptions: browserLaunchOptions.providerOptions
|
|
3571
3708
|
});
|
|
3572
3709
|
return {
|
|
3573
|
-
|
|
3574
|
-
|
|
3710
|
+
projectServers,
|
|
3711
|
+
containerServer,
|
|
3575
3712
|
browser: runtime.browser,
|
|
3576
3713
|
browserLaunchOptions,
|
|
3577
|
-
port,
|
|
3578
3714
|
wsPort,
|
|
3579
|
-
manifestPath,
|
|
3580
3715
|
tempDir,
|
|
3581
|
-
manifestPlugin: virtualManifestPlugin,
|
|
3582
3716
|
setContainerOptions,
|
|
3583
3717
|
dispatchHandlers,
|
|
3584
3718
|
wss
|
|
3585
3719
|
};
|
|
3586
3720
|
} catch (error) {
|
|
3587
3721
|
wss.close();
|
|
3588
|
-
await
|
|
3722
|
+
await closeAllProjectServers(projectServers.values());
|
|
3589
3723
|
throw error;
|
|
3590
3724
|
}
|
|
3591
3725
|
};
|
|
@@ -3756,11 +3890,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
3756
3890
|
const enableCliShortcuts = isWatchMode && isBrowserWatchCliShortcutsEnabled();
|
|
3757
3891
|
const browserTempOutputRoot = context.normalizedConfig.output.distPath.root;
|
|
3758
3892
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, browserTempOutputRoot, 'browser', 'watch') : join(context.rootPath, browserTempOutputRoot, 'browser', Date.now().toString());
|
|
3759
|
-
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
3760
|
-
const manifestSource = generateManifestModule({
|
|
3761
|
-
manifestPath,
|
|
3762
|
-
entries: projectEntries
|
|
3763
|
-
});
|
|
3764
3893
|
if (isWatchMode) watchContext.lastTestFiles = collectWatchTestFiles(projectEntries);
|
|
3765
3894
|
let runtime = isWatchMode ? watchContext.runtime : null;
|
|
3766
3895
|
let triggerRerun;
|
|
@@ -3768,8 +3897,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3768
3897
|
try {
|
|
3769
3898
|
runtime = await createBrowserRuntime({
|
|
3770
3899
|
context,
|
|
3771
|
-
|
|
3772
|
-
manifestSource,
|
|
3900
|
+
projectEntries,
|
|
3773
3901
|
tempDir,
|
|
3774
3902
|
isWatchMode,
|
|
3775
3903
|
onTriggerRerun: isWatchMode ? async ()=>{
|
|
@@ -3794,7 +3922,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3794
3922
|
});
|
|
3795
3923
|
}
|
|
3796
3924
|
}
|
|
3797
|
-
const { browser, browserLaunchOptions,
|
|
3925
|
+
const { browser, browserLaunchOptions, wsPort, wss } = runtime;
|
|
3798
3926
|
const buildTime = Date.now() - buildStart;
|
|
3799
3927
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
3800
3928
|
testPath: normalize(testPath),
|
|
@@ -3808,13 +3936,20 @@ const runBrowserController = async (context, options)=>{
|
|
|
3808
3936
|
viewport: project.normalizedConfig.browser.viewport
|
|
3809
3937
|
}));
|
|
3810
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
|
+
]));
|
|
3811
3945
|
const hostOptions = {
|
|
3812
3946
|
rootPath: normalize(context.rootPath),
|
|
3813
3947
|
projects: projectRuntimeConfigs,
|
|
3814
3948
|
snapshot: {
|
|
3815
3949
|
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
3816
3950
|
},
|
|
3817
|
-
runnerUrl: `http://localhost:${port}`,
|
|
3951
|
+
runnerUrl: `http://localhost:${runtime.containerServer.port}`,
|
|
3952
|
+
projectRunnerUrls,
|
|
3818
3953
|
wsPort,
|
|
3819
3954
|
debug: isDebug(),
|
|
3820
3955
|
rpcTimeout: maxTestTimeoutForRpc
|
|
@@ -4133,11 +4268,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
4133
4268
|
const donePromise = new Promise((resolve)=>{
|
|
4134
4269
|
resolveDone = resolve;
|
|
4135
4270
|
});
|
|
4136
|
-
const
|
|
4137
|
-
const
|
|
4138
|
-
|
|
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
|
+
};
|
|
4139
4277
|
try {
|
|
4140
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}.`));
|
|
4141
4281
|
const session = sessionRegistry.register({
|
|
4142
4282
|
testFile: file.testPath,
|
|
4143
4283
|
projectName: file.projectName,
|
|
@@ -4184,21 +4324,27 @@ const runBrowserController = async (context, options)=>{
|
|
|
4184
4324
|
};
|
|
4185
4325
|
const serializedOptions = serializeForInlineScript(inlineOptions);
|
|
4186
4326
|
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
4187
|
-
|
|
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`, {
|
|
4188
4330
|
waitUntil: 'load'
|
|
4189
4331
|
});
|
|
4190
|
-
const timeoutPromise = new Promise((resolve)=>{
|
|
4191
|
-
timeoutId = setTimeout(()=>resolve('timeout'), perFileTimeoutMs);
|
|
4192
|
-
});
|
|
4193
4332
|
const state = await Promise.race([
|
|
4194
|
-
donePromise.then(()=>
|
|
4195
|
-
|
|
4196
|
-
|
|
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
|
+
}))
|
|
4197
4343
|
]);
|
|
4198
|
-
if ('cancelled' === state) return;
|
|
4199
|
-
if ('
|
|
4344
|
+
if ('cancelled' === state.type) return;
|
|
4345
|
+
if ('crash' === state.type && runLifecycle.isTokenActive(run.token) && !run.cancelled) {
|
|
4200
4346
|
await handleFatal({
|
|
4201
|
-
message:
|
|
4347
|
+
message: state.reason
|
|
4202
4348
|
});
|
|
4203
4349
|
await cancelRun(run, false);
|
|
4204
4350
|
}
|
|
@@ -4212,7 +4358,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4212
4358
|
await cancelRun(run, false);
|
|
4213
4359
|
}
|
|
4214
4360
|
} finally{
|
|
4215
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
4216
4361
|
if (page) try {
|
|
4217
4362
|
await page.close();
|
|
4218
4363
|
} catch {}
|
|
@@ -4455,10 +4600,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4455
4600
|
if (!fileInfo) throw new Error(`Unknown browser test file: ${JSON.stringify(testFile)}`);
|
|
4456
4601
|
return fileInfo;
|
|
4457
4602
|
};
|
|
4458
|
-
const getHeadedPerFileTimeoutMs = (file)=>{
|
|
4459
|
-
const projectRuntime = projectRuntimeConfigs.find((project)=>project.name === file.projectName);
|
|
4460
|
-
return (projectRuntime?.runtimeConfig.testTimeout ?? maxTestTimeoutForRpc) + PER_FILE_TIMEOUT_BUFFER_MS;
|
|
4461
|
-
};
|
|
4462
4603
|
let containerContext;
|
|
4463
4604
|
let containerPage;
|
|
4464
4605
|
let isNewPage = false;
|
|
@@ -4528,26 +4669,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
4528
4669
|
pendingHeadedReloads.delete(testPath);
|
|
4529
4670
|
pending.deferred.resolve();
|
|
4530
4671
|
};
|
|
4531
|
-
const
|
|
4532
|
-
const timeoutMs = getHeadedPerFileTimeoutMs(file);
|
|
4533
|
-
let timeoutId;
|
|
4672
|
+
const reloadTestFileAndWait = async (file, testNamePattern)=>{
|
|
4534
4673
|
let reloadAck;
|
|
4535
4674
|
try {
|
|
4536
4675
|
reloadAck = await rpcManager.reloadTestFile(file.testPath, testNamePattern);
|
|
4537
|
-
|
|
4538
|
-
await Promise.race([
|
|
4539
|
-
completionPromise,
|
|
4540
|
-
new Promise((_, reject)=>{
|
|
4541
|
-
timeoutId = setTimeout(()=>{
|
|
4542
|
-
reject(new Error(`Headed test execution timeout after ${timeoutMs / 1000}s for ${file.testPath}.`));
|
|
4543
|
-
}, timeoutMs);
|
|
4544
|
-
})
|
|
4545
|
-
]);
|
|
4676
|
+
await registerPendingHeadedReload(file.testPath, reloadAck.runId);
|
|
4546
4677
|
} catch (error) {
|
|
4547
4678
|
if (reloadAck?.runId) rejectPendingHeadedReload(file.testPath, toError(error), reloadAck.runId);
|
|
4548
4679
|
throw error;
|
|
4549
|
-
} finally{
|
|
4550
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
4551
4680
|
}
|
|
4552
4681
|
};
|
|
4553
4682
|
const createRpcMethods = ()=>({
|
|
@@ -4604,14 +4733,15 @@ const runBrowserController = async (context, options)=>{
|
|
|
4604
4733
|
}
|
|
4605
4734
|
if (isNewPage) {
|
|
4606
4735
|
const pagePath = '/';
|
|
4607
|
-
|
|
4736
|
+
const containerPort = runtime.containerServer.port;
|
|
4737
|
+
await containerPage.goto(`http://localhost:${containerPort}${pagePath}`, {
|
|
4608
4738
|
waitUntil: 'load'
|
|
4609
4739
|
});
|
|
4610
|
-
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${
|
|
4740
|
+
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${containerPort}${pagePath}\n`));
|
|
4611
4741
|
}
|
|
4612
4742
|
enqueueHeadedReload = async (file, testNamePattern)=>headedReloadQueue.enqueue(async ()=>{
|
|
4613
4743
|
if (fatalError) return;
|
|
4614
|
-
await
|
|
4744
|
+
await reloadTestFileAndWait(file, testNamePattern);
|
|
4615
4745
|
});
|
|
4616
4746
|
let testTime = 0;
|
|
4617
4747
|
if (currentTestFiles.length > 0) {
|
|
@@ -4732,18 +4862,12 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4732
4862
|
close: async ()=>{}
|
|
4733
4863
|
};
|
|
4734
4864
|
const tempDir = join(context.rootPath, context.normalizedConfig.output.distPath.root, 'browser', `list-${Date.now()}`);
|
|
4735
|
-
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
4736
|
-
const manifestSource = generateManifestModule({
|
|
4737
|
-
manifestPath,
|
|
4738
|
-
entries: projectEntries
|
|
4739
|
-
});
|
|
4740
4865
|
const browserProjects = getBrowserProjects(context);
|
|
4741
4866
|
let runtime;
|
|
4742
4867
|
try {
|
|
4743
4868
|
runtime = await createBrowserRuntime({
|
|
4744
4869
|
context,
|
|
4745
|
-
|
|
4746
|
-
manifestSource,
|
|
4870
|
+
projectEntries,
|
|
4747
4871
|
tempDir,
|
|
4748
4872
|
isWatchMode: false,
|
|
4749
4873
|
containerDistPath: void 0,
|
|
@@ -4757,7 +4881,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4757
4881
|
logger.error(color.red(`Failed to initialize browser provider runtime (${providers.join(', ')}).`), error);
|
|
4758
4882
|
throw error;
|
|
4759
4883
|
}
|
|
4760
|
-
const { browser, browserLaunchOptions
|
|
4884
|
+
const { browser, browserLaunchOptions } = runtime;
|
|
4761
4885
|
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
4762
4886
|
name: project.name,
|
|
4763
4887
|
environmentName: project.environmentName,
|
|
@@ -4777,70 +4901,81 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4777
4901
|
rpcTimeout: maxTestTimeoutForRpc
|
|
4778
4902
|
};
|
|
4779
4903
|
runtime.setContainerOptions(hostOptions);
|
|
4780
|
-
const collectResults = [];
|
|
4781
|
-
let fatalError = null;
|
|
4782
|
-
let collectCompleted = false;
|
|
4783
|
-
let resolveCollect;
|
|
4784
|
-
const collectPromise = new Promise((resolve)=>{
|
|
4785
|
-
resolveCollect = resolve;
|
|
4786
|
-
});
|
|
4787
4904
|
const browserContext = await browser.newContext({
|
|
4788
4905
|
providerOptions: browserLaunchOptions.providerOptions,
|
|
4789
4906
|
viewport: null
|
|
4790
4907
|
});
|
|
4791
|
-
const
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
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;
|
|
4813
4932
|
resolveCollect?.();
|
|
4814
4933
|
break;
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
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;
|
|
4841
4977
|
const cleanup = async ()=>{
|
|
4842
4978
|
try {
|
|
4843
|
-
await page.close();
|
|
4844
4979
|
await browserContext.close();
|
|
4845
4980
|
} catch {}
|
|
4846
4981
|
await destroyBrowserRuntime(runtime);
|