@midscene/test 1.16.0 → 1.16.1
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/es/cli/index.mjs +389 -129
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +377 -118
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/cli/index.js +381 -118
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/test-cli.js +376 -117
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/templates/test/android/README.md +2 -0
- package/dist/templates/test/android/README.zh.md +2 -0
- package/dist/templates/test/android/midscene.config.ts +28 -31
- package/dist/templates/test/computer/README.md +2 -0
- package/dist/templates/test/computer/README.zh.md +2 -0
- package/dist/templates/test/computer/midscene.config.ts +31 -34
- package/dist/templates/test/harmony/README.md +2 -0
- package/dist/templates/test/harmony/README.zh.md +2 -0
- package/dist/templates/test/harmony/midscene.config.ts +28 -31
- package/dist/templates/test/ios/README.md +2 -0
- package/dist/templates/test/ios/README.zh.md +2 -0
- package/dist/templates/test/ios/midscene.config.ts +28 -31
- package/dist/templates/test/web/README.md +16 -0
- package/dist/templates/test/web/README.zh.md +16 -0
- package/dist/templates/test/web/cases/todo.yaml +2 -0
- package/dist/templates/test/web/midscene.config.ts +34 -12
- package/dist/types/cli/index.d.ts +32 -11
- package/dist/types/index.d.ts +3 -3
- package/package.json +13 -13
package/dist/lib/cli/index.js
CHANGED
|
@@ -28,11 +28,12 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
28
|
createTestRunId: ()=>createTestRunId,
|
|
29
29
|
runTestProject: ()=>runTestProject,
|
|
30
30
|
discoverTestConfig: ()=>discoverTestConfig,
|
|
31
|
+
defineFileSetup: ()=>defineFileSetup,
|
|
31
32
|
validateTestFileSelection: ()=>validateTestFileSelection,
|
|
32
33
|
defineProjectSetup: ()=>defineProjectSetup,
|
|
33
34
|
discoverTestFiles: ()=>discoverTestFiles,
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
defineTestProject: ()=>defineTestProject,
|
|
36
|
+
loadTestProject: ()=>loadTestProject
|
|
36
37
|
});
|
|
37
38
|
const external_node_path_namespaceObject = require("node:path");
|
|
38
39
|
const external_node_url_namespaceObject = require("node:url");
|
|
@@ -42,6 +43,7 @@ const NodeRegistry = test_runner_namespaceObject.NodeRegistry;
|
|
|
42
43
|
const projectIdFromIndex = (index)=>`project-${index}`;
|
|
43
44
|
const defineTestProject = (definition)=>definition;
|
|
44
45
|
const defineProjectSetup = (definition)=>definition;
|
|
46
|
+
const defineFileSetup = (definition)=>definition;
|
|
45
47
|
const getDefaultExport = (loaded, absolutePath)=>{
|
|
46
48
|
if ('object' == typeof loaded && null !== loaded && 'default' in loaded) {
|
|
47
49
|
const defaultExport = loaded.default;
|
|
@@ -158,9 +160,19 @@ const validateNonNegativeInteger = (value, fallback, label)=>{
|
|
|
158
160
|
if (!Number.isInteger(value) || value < 0) throw new TypeError(`Midscene config ${label} must be a non-negative integer.`);
|
|
159
161
|
return value;
|
|
160
162
|
};
|
|
161
|
-
const
|
|
163
|
+
const validateConcurrency = (value, label)=>{
|
|
164
|
+
if (null === value) return null;
|
|
165
|
+
return validatePositiveInteger(value, 1, label);
|
|
166
|
+
};
|
|
167
|
+
const validateFileConcurrency = (maxConcurrentFiles, fileSetup, label)=>{
|
|
168
|
+
if (maxConcurrentFiles > 1 && !fileSetup) throw new TypeError(`Midscene config ${label}.fileSetup is required when the effective maxConcurrentFiles is greater than 1.`);
|
|
169
|
+
};
|
|
170
|
+
const validateExecutionProjects = (value, defaultSetup, defaultFileSetup, globalNodes, test)=>{
|
|
162
171
|
if (void 0 === value) {
|
|
163
172
|
const setup = validateSetup(defaultSetup, 'setup');
|
|
173
|
+
const fileSetup = validateSetup(defaultFileSetup, 'fileSetup');
|
|
174
|
+
const maxConcurrentFiles = test.maxConcurrentFiles ?? 1 / 0;
|
|
175
|
+
validateFileConcurrency(maxConcurrentFiles, fileSetup, 'default');
|
|
164
176
|
return {
|
|
165
177
|
hasExplicitProjects: false,
|
|
166
178
|
projects: Object.freeze([
|
|
@@ -170,6 +182,10 @@ const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
|
170
182
|
...setup ? {
|
|
171
183
|
setup
|
|
172
184
|
} : {},
|
|
185
|
+
...fileSetup ? {
|
|
186
|
+
fileSetup
|
|
187
|
+
} : {},
|
|
188
|
+
maxConcurrentFiles,
|
|
173
189
|
tags: Object.freeze({
|
|
174
190
|
include: [],
|
|
175
191
|
exclude: []
|
|
@@ -182,6 +198,7 @@ const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
|
182
198
|
};
|
|
183
199
|
}
|
|
184
200
|
if (void 0 !== defaultSetup) throw new TypeError('Midscene config setup cannot be used together with projects. Move setup to projects[].setup.');
|
|
201
|
+
if (void 0 !== defaultFileSetup) throw new TypeError('Midscene config fileSetup cannot be used together with projects. Move fileSetup to projects[].fileSetup.');
|
|
185
202
|
if (!Array.isArray(value) || 0 === value.length) throw new TypeError('Midscene config projects must be a non-empty array when declared.');
|
|
186
203
|
const names = new Set();
|
|
187
204
|
const projects = value.map((candidate, index)=>{
|
|
@@ -190,6 +207,8 @@ const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
|
190
207
|
rejectUnknownKeys(candidate, [
|
|
191
208
|
'name',
|
|
192
209
|
'setup',
|
|
210
|
+
'fileSetup',
|
|
211
|
+
'maxConcurrentFiles',
|
|
193
212
|
'nodes',
|
|
194
213
|
'files',
|
|
195
214
|
'tags',
|
|
@@ -206,10 +225,18 @@ const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
|
206
225
|
...globalNodes.definitions().filter((node)=>!localNodes.has(node.name)),
|
|
207
226
|
...localNodes.definitions()
|
|
208
227
|
]);
|
|
228
|
+
const fileSetup = validateSetup(candidate.fileSetup, `${label}.fileSetup`);
|
|
229
|
+
const globalFileLimit = test.maxConcurrentFiles ?? 1 / 0;
|
|
230
|
+
const maxConcurrentFiles = Math.min(globalFileLimit, validatePositiveInteger(candidate.maxConcurrentFiles, globalFileLimit, `${label}.maxConcurrentFiles`));
|
|
231
|
+
validateFileConcurrency(maxConcurrentFiles, fileSetup, label);
|
|
209
232
|
return Object.freeze({
|
|
210
233
|
projectId: projectIdFromIndex(index),
|
|
211
234
|
name: candidate.name,
|
|
212
235
|
nodes,
|
|
236
|
+
...fileSetup ? {
|
|
237
|
+
fileSetup
|
|
238
|
+
} : {},
|
|
239
|
+
maxConcurrentFiles,
|
|
213
240
|
...files ? {
|
|
214
241
|
files
|
|
215
242
|
} : {},
|
|
@@ -226,11 +253,25 @@ const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
|
226
253
|
hasExplicitProjects: true
|
|
227
254
|
};
|
|
228
255
|
};
|
|
229
|
-
const validateTestOptions = (value)=>{
|
|
256
|
+
const validateTestOptions = (value, hasExplicitProjects)=>{
|
|
230
257
|
if (void 0 !== value && !isRecord(value)) throw new TypeError('Midscene config test must be an object.');
|
|
231
258
|
const candidate = value ?? {};
|
|
259
|
+
if (void 0 !== candidate.maxConcurrency) {
|
|
260
|
+
const limit = 'number' == typeof candidate.maxConcurrency && Number.isInteger(candidate.maxConcurrency) && candidate.maxConcurrency > 0 ? candidate.maxConcurrency : 1;
|
|
261
|
+
throw new TypeError([
|
|
262
|
+
'Midscene config test.maxConcurrency is no longer supported.',
|
|
263
|
+
'It controlled project concurrency, not file concurrency. Remove it and use:',
|
|
264
|
+
` test: { maxConcurrentProjects: ${limit}, maxConcurrentFiles: ${hasExplicitProjects ? limit : 1} }`,
|
|
265
|
+
hasExplicitProjects ? ' Set projects[].maxConcurrentFiles to 1 to keep files sequential within each project.' : ' The implicit default project keeps running files sequentially.',
|
|
266
|
+
hasExplicitProjects ? 'For parallel files, configure fileSetup and raise both test.maxConcurrentFiles and projects[].maxConcurrentFiles.' : 'For parallel files, configure fileSetup and raise test.maxConcurrentFiles.',
|
|
267
|
+
'Migration guide: https://midscenejs.com/midscene-test/configuration.html#existing-configurations'
|
|
268
|
+
].join('\n'));
|
|
269
|
+
}
|
|
232
270
|
return Object.freeze({
|
|
233
|
-
|
|
271
|
+
maxConcurrentFiles: validateConcurrency(candidate.maxConcurrentFiles, 'test.maxConcurrentFiles'),
|
|
272
|
+
maxConcurrentProjects: validateConcurrency(candidate.maxConcurrentProjects, 'test.maxConcurrentProjects'),
|
|
273
|
+
setupTimeout: validatePositiveInteger(candidate.setupTimeout, 120000, 'test.setupTimeout'),
|
|
274
|
+
teardownTimeout: validatePositiveInteger(candidate.teardownTimeout, 30000, 'test.teardownTimeout'),
|
|
234
275
|
bail: validateNonNegativeInteger(candidate.bail, 0, 'test.bail'),
|
|
235
276
|
testTimeout: validatePositiveInteger(candidate.testTimeout, 120000, 'test.testTimeout')
|
|
236
277
|
});
|
|
@@ -260,16 +301,18 @@ const validateTestProjectDefinition = (definition)=>{
|
|
|
260
301
|
if ('testRunner' in definition) throw new TypeError('Midscene config testRunner is not supported. Rename it to test.');
|
|
261
302
|
rejectUnknownKeys(definition, [
|
|
262
303
|
'setup',
|
|
304
|
+
'fileSetup',
|
|
263
305
|
'projects',
|
|
264
306
|
'test',
|
|
265
307
|
'output',
|
|
266
308
|
'nodes'
|
|
267
309
|
], 'root');
|
|
268
310
|
const nodes = new NodeRegistry(definition.nodes);
|
|
269
|
-
const
|
|
311
|
+
const test = validateTestOptions(definition.test, void 0 !== definition.projects);
|
|
312
|
+
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup, definition.fileSetup, nodes, test);
|
|
270
313
|
return {
|
|
271
314
|
...resolvedProjects,
|
|
272
|
-
test
|
|
315
|
+
test,
|
|
273
316
|
output: validateOutput(definition.output),
|
|
274
317
|
nodes,
|
|
275
318
|
resolveNode: (name)=>nodes.get(name)
|
|
@@ -953,7 +996,24 @@ async function prepareProject(project, projectRoot, runDir, defaultTimeoutMs, op
|
|
|
953
996
|
defaultTimeoutMs,
|
|
954
997
|
reportEnabled: true,
|
|
955
998
|
bindings: {
|
|
956
|
-
resolveNode: project.nodes.require.bind(project.nodes)
|
|
999
|
+
resolveNode: project.nodes.require.bind(project.nodes),
|
|
1000
|
+
...project.fileSetup ? {
|
|
1001
|
+
documentSetup: {
|
|
1002
|
+
name: project.fileSetup.name,
|
|
1003
|
+
setup ({ projectContext, document, signal, onTeardown }) {
|
|
1004
|
+
return project.fileSetup.setup({
|
|
1005
|
+
project,
|
|
1006
|
+
projectContext,
|
|
1007
|
+
file: document,
|
|
1008
|
+
env: Object.freeze({
|
|
1009
|
+
...process.env
|
|
1010
|
+
}),
|
|
1011
|
+
signal,
|
|
1012
|
+
onTeardown
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
} : {}
|
|
957
1017
|
}
|
|
958
1018
|
});
|
|
959
1019
|
} catch (error) {
|
|
@@ -1159,11 +1219,40 @@ const documentHasFatalError = (result)=>[
|
|
|
1159
1219
|
...result.beforeAll,
|
|
1160
1220
|
...result.afterAll
|
|
1161
1221
|
].some((step)=>step.error && (0, test_runner_namespaceObject.isFatalDeviceError)(step.error)) || (result.teardownErrors ?? []).some(test_runner_namespaceObject.isFatalDeviceError) || !!result.executionErrors?.some(test_runner_namespaceObject.isResourceRecoveryError);
|
|
1222
|
+
const isSharedResourceFailure = (error)=>{
|
|
1223
|
+
const seen = new Set();
|
|
1224
|
+
const pending = [
|
|
1225
|
+
error
|
|
1226
|
+
];
|
|
1227
|
+
while(pending.length && seen.size < 50){
|
|
1228
|
+
const item = pending.pop();
|
|
1229
|
+
if ('object' != typeof item || null === item || seen.has(item)) continue;
|
|
1230
|
+
seen.add(item);
|
|
1231
|
+
const failure = item;
|
|
1232
|
+
if ('PROJECT_RESOURCE_ERROR' === failure.code) return true;
|
|
1233
|
+
if (failure.cause) pending.push(failure.cause);
|
|
1234
|
+
if (Array.isArray(failure.errors)) pending.push(...failure.errors.slice(0, 50));
|
|
1235
|
+
}
|
|
1236
|
+
return false;
|
|
1237
|
+
};
|
|
1162
1238
|
async function executeDocumentInvocation(options) {
|
|
1163
1239
|
const { invocation, project, sinks } = options;
|
|
1240
|
+
const isProjectResourceFailure = options.isProjectResourceFailure ?? (()=>false);
|
|
1164
1241
|
const { document } = invocation;
|
|
1165
1242
|
const documentRetry = 'document' === invocation.retry.scope;
|
|
1166
1243
|
const attempts = [];
|
|
1244
|
+
const fileSetup = invocation.bindings.documentSetup;
|
|
1245
|
+
const documentSetup = options.isProjectResourceFailure && fileSetup ? {
|
|
1246
|
+
...fileSetup,
|
|
1247
|
+
async setup (ctx) {
|
|
1248
|
+
try {
|
|
1249
|
+
return await fileSetup.setup(ctx);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
if (isProjectResourceFailure(error)) options.markProjectFatal();
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
} : fileSetup;
|
|
1167
1256
|
await (0, test_runner_namespaceObject.runDocumentAttempts)({
|
|
1168
1257
|
retry: documentRetry ? invocation.retry.count : 0,
|
|
1169
1258
|
signal: options.signal,
|
|
@@ -1174,6 +1263,9 @@ async function executeDocumentInvocation(options) {
|
|
|
1174
1263
|
plan: invocation.plan,
|
|
1175
1264
|
documentAttemptIndex: documentRetry ? documentAttemptIndex : void 0,
|
|
1176
1265
|
...invocation.bindings,
|
|
1266
|
+
documentSetup,
|
|
1267
|
+
setupTimeoutMs: options.setupTimeoutMs,
|
|
1268
|
+
teardownTimeoutMs: options.teardownTimeoutMs,
|
|
1177
1269
|
project,
|
|
1178
1270
|
projectContext: options.projectContext,
|
|
1179
1271
|
retry: documentRetry ? 0 : invocation.retry.count,
|
|
@@ -1185,7 +1277,7 @@ async function executeDocumentInvocation(options) {
|
|
|
1185
1277
|
...run.beforeEach,
|
|
1186
1278
|
...run.steps,
|
|
1187
1279
|
...run.afterEach
|
|
1188
|
-
].some((step)=>step.error && (0, test_runner_namespaceObject.isFatalDeviceError)(step.error)) || (run.teardownErrors ?? []).some(test_runner_namespaceObject.isFatalDeviceError),
|
|
1280
|
+
].some((step)=>step.error && ((0, test_runner_namespaceObject.isFatalDeviceError)(step.error) || isProjectResourceFailure(step.error))) || (run.teardownErrors ?? []).some((error)=>(0, test_runner_namespaceObject.isFatalDeviceError)(error) || isProjectResourceFailure(error)),
|
|
1189
1281
|
onCaseStart: (collectedCase)=>{
|
|
1190
1282
|
options.onProgress(` [case ${collectedCase.caseIndex + 1}/${document.cases.length}] ${collectedCase.definition.name}`);
|
|
1191
1283
|
},
|
|
@@ -1194,6 +1286,7 @@ async function executeDocumentInvocation(options) {
|
|
|
1194
1286
|
options.onProgress(`${indent}→ ${formatStep(info)}`);
|
|
1195
1287
|
},
|
|
1196
1288
|
onStepResult: async (info, result)=>{
|
|
1289
|
+
if (isProjectResourceFailure(result.error)) options.markProjectFatal();
|
|
1197
1290
|
await invocation.bindings.onStepResult?.(info, result);
|
|
1198
1291
|
options.onProgress(formatStepResult(info, result));
|
|
1199
1292
|
},
|
|
@@ -1202,7 +1295,12 @@ async function executeDocumentInvocation(options) {
|
|
|
1202
1295
|
options.onProgress(` ${'success' === attempt.status ? '✓' : '✗'} attempt ${attempt.attemptIndex + 1}/${documentRetry ? 1 : invocation.retry.count + 1}: ${attempt.name} (${attempt.durationMs} ms)`);
|
|
1203
1296
|
},
|
|
1204
1297
|
onCaseOutcome: (outcome)=>{
|
|
1205
|
-
if (
|
|
1298
|
+
if (outcome.attempts?.some((attempt)=>[
|
|
1299
|
+
...attempt.beforeEach,
|
|
1300
|
+
...attempt.steps,
|
|
1301
|
+
...attempt.afterEach
|
|
1302
|
+
].some((step)=>isProjectResourceFailure(step.error)) || attempt.teardownErrors?.some(isProjectResourceFailure))) options.markProjectFatal();
|
|
1303
|
+
if (!options.isolatedContext && caseHasFatalError(outcome)) options.markProjectFatal();
|
|
1206
1304
|
if (!documentRetry && 'failed' === outcome.status) options.addFailedCases(1);
|
|
1207
1305
|
},
|
|
1208
1306
|
onDocumentResult: options.onDocumentResult
|
|
@@ -1211,7 +1309,7 @@ async function executeDocumentInvocation(options) {
|
|
|
1211
1309
|
const partial = error.result;
|
|
1212
1310
|
const resourceOnly = error.errors.length > 0 && error.errors.every((failure)=>(0, test_runner_namespaceObject.isResourceRecoveryError)(failure) || partial.document.hostErrors?.some((host)=>'cleanup' === host.phase && (host.error === failure || host.error instanceof Error && host.error.cause === failure)));
|
|
1213
1311
|
if (resourceOnly) {
|
|
1214
|
-
if (error.errors.some(test_runner_namespaceObject.isResourceRecoveryError)) options.markProjectFatal();
|
|
1312
|
+
if (!options.isolatedContext && error.errors.some(test_runner_namespaceObject.isResourceRecoveryError)) options.markProjectFatal();
|
|
1215
1313
|
return partial;
|
|
1216
1314
|
}
|
|
1217
1315
|
if (documentRetry) partial.document.retryDecision = (0, test_runner_namespaceObject.decideRetry)({
|
|
@@ -1240,7 +1338,17 @@ async function executeDocumentInvocation(options) {
|
|
|
1240
1338
|
}
|
|
1241
1339
|
throw error;
|
|
1242
1340
|
});
|
|
1243
|
-
if (documentHasFatalError(execution.document)) options.markProjectFatal();
|
|
1341
|
+
if (!options.isolatedContext && documentHasFatalError(execution.document)) options.markProjectFatal();
|
|
1342
|
+
if ([
|
|
1343
|
+
...execution.document.beforeAll,
|
|
1344
|
+
...execution.document.afterAll,
|
|
1345
|
+
...(execution.document.hostErrors ?? []).map(({ error })=>({
|
|
1346
|
+
error
|
|
1347
|
+
})),
|
|
1348
|
+
...(execution.document.teardownErrors ?? []).map((error)=>({
|
|
1349
|
+
error
|
|
1350
|
+
}))
|
|
1351
|
+
].some(({ error })=>isProjectResourceFailure(error))) options.markProjectFatal();
|
|
1244
1352
|
const owners = [
|
|
1245
1353
|
execution.document,
|
|
1246
1354
|
...execution.cases.flatMap((outcome)=>outcome.attempts ?? [])
|
|
@@ -1248,7 +1356,10 @@ async function executeDocumentInvocation(options) {
|
|
|
1248
1356
|
const cleanupPending = owners.some((owner)=>owner.cleanup?.status === 'pending');
|
|
1249
1357
|
const operationPending = owners.some((owner)=>!!owner.pendingOperations?.length);
|
|
1250
1358
|
const cleanupFailed = owners.some((owner)=>owner.cleanup?.status === 'failed' || !!owner.teardownErrors?.length || 'hostErrors' in owner && owner.hostErrors?.some(({ phase })=>'cleanup' === phase));
|
|
1251
|
-
if (cleanupPending ||
|
|
1359
|
+
if (cleanupPending || operationPending) {
|
|
1360
|
+
options.markProjectFatal();
|
|
1361
|
+
options.onUnresolvedResources?.();
|
|
1362
|
+
} else if (cleanupFailed && !options.isolatedContext) options.markProjectFatal();
|
|
1252
1363
|
const failed = 'failed' === execution.document.status || execution.cases.some((item)=>'success' !== item.status);
|
|
1253
1364
|
const decision = (0, test_runner_namespaceObject.decideRetry)({
|
|
1254
1365
|
scope: 'document',
|
|
@@ -1301,6 +1412,231 @@ async function executeDocumentInvocation(options) {
|
|
|
1301
1412
|
if (documentRetry && failedCases) options.addFailedCases(failedCases);
|
|
1302
1413
|
else if ('failed' === final.document.status && 0 === failedCases) options.addFailedCases(Math.max(1, failedCases));
|
|
1303
1414
|
}
|
|
1415
|
+
async function runPreparedFiles(options) {
|
|
1416
|
+
const states = options.projects.map((prepared)=>({
|
|
1417
|
+
prepared,
|
|
1418
|
+
cases: [],
|
|
1419
|
+
documents: [],
|
|
1420
|
+
cursor: 0,
|
|
1421
|
+
active: 0,
|
|
1422
|
+
initializing: false,
|
|
1423
|
+
finished: false,
|
|
1424
|
+
fatal: false,
|
|
1425
|
+
setupFailed: false
|
|
1426
|
+
}));
|
|
1427
|
+
let nextProject = 0;
|
|
1428
|
+
let resourcesUnresolved = false;
|
|
1429
|
+
let occupied = 0;
|
|
1430
|
+
let occupiedProjects = 0;
|
|
1431
|
+
const cleanupQueue = [];
|
|
1432
|
+
const release = ()=>{
|
|
1433
|
+
occupied--;
|
|
1434
|
+
while(occupied < options.maxConcurrentFiles && cleanupQueue.length){
|
|
1435
|
+
occupied++;
|
|
1436
|
+
cleanupQueue.shift()();
|
|
1437
|
+
}
|
|
1438
|
+
wake?.();
|
|
1439
|
+
};
|
|
1440
|
+
let wake;
|
|
1441
|
+
const running = new Set();
|
|
1442
|
+
const stopped = (state)=>options.signal.aborted || options.shouldBail() || state.fatal;
|
|
1443
|
+
const invalid = (state)=>options.preflightFailed || state.prepared.collectionErrors.length > 0;
|
|
1444
|
+
const unresolved = ()=>{
|
|
1445
|
+
if (resourcesUnresolved) return;
|
|
1446
|
+
resourcesUnresolved = true;
|
|
1447
|
+
options.onInfrastructureError(new test_runner_namespaceObject.WorkflowError('Resources are still running or cleanup is pending. No new test files will start.', {
|
|
1448
|
+
code: 'UNRESOLVED_FILE_RESOURCES'
|
|
1449
|
+
}));
|
|
1450
|
+
};
|
|
1451
|
+
const finishProject = async (state)=>{
|
|
1452
|
+
if (!state.runtime || state.finished) return;
|
|
1453
|
+
state.finished = true;
|
|
1454
|
+
const failed = state.fatal || options.signal.aborted || state.cases.some((item)=>'success' !== item.status) || state.documents.some((item)=>'success' !== item.status);
|
|
1455
|
+
state.lifecycle = await state.runtime.finish(failed ? 'failed' : 'success');
|
|
1456
|
+
const releaseProject = ()=>{
|
|
1457
|
+
occupiedProjects--;
|
|
1458
|
+
wake?.();
|
|
1459
|
+
};
|
|
1460
|
+
if (state.lifecycle.cleanup?.status === 'pending' || state.lifecycle.pendingOperations?.length) {
|
|
1461
|
+
unresolved();
|
|
1462
|
+
Promise.all([
|
|
1463
|
+
(0, test_runner_namespaceObject.waitForScopeOperations)(state.runtime.signal),
|
|
1464
|
+
(0, test_runner_namespaceObject.getResourceCleanupCompletion)(state.runtime.signal)
|
|
1465
|
+
]).then(releaseProject, (error)=>{
|
|
1466
|
+
options.onInfrastructureError(error);
|
|
1467
|
+
releaseProject();
|
|
1468
|
+
});
|
|
1469
|
+
} else releaseProject();
|
|
1470
|
+
};
|
|
1471
|
+
const admit = ()=>{
|
|
1472
|
+
for(let offset = 0; offset < states.length; offset++){
|
|
1473
|
+
const index = (nextProject + offset) % states.length;
|
|
1474
|
+
const state = states[index];
|
|
1475
|
+
if (invalid(state) || state.finished || state.initializing) continue;
|
|
1476
|
+
if (!state.runtime && occupiedProjects >= options.maxConcurrentProjects) continue;
|
|
1477
|
+
const cleanupOnly = !!state.runtime && 0 === state.active && (stopped(state) || state.cursor === state.prepared.invocations.length);
|
|
1478
|
+
if (!cleanupOnly && (stopped(state) || state.cursor >= state.prepared.invocations.length || state.active >= (state.prepared.project.maxConcurrentFiles ?? options.maxConcurrentFiles))) continue;
|
|
1479
|
+
nextProject = (index + 1) % states.length;
|
|
1480
|
+
occupied++;
|
|
1481
|
+
state.active++;
|
|
1482
|
+
if (!state.runtime && !cleanupOnly) {
|
|
1483
|
+
occupiedProjects++;
|
|
1484
|
+
state.initializing = true;
|
|
1485
|
+
}
|
|
1486
|
+
const invocationIndex = cleanupOnly ? -1 : state.cursor++;
|
|
1487
|
+
return {
|
|
1488
|
+
state,
|
|
1489
|
+
index,
|
|
1490
|
+
invocationIndex
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
const run = async ({ state, index, invocationIndex })=>{
|
|
1495
|
+
const { prepared } = state;
|
|
1496
|
+
const { project } = prepared;
|
|
1497
|
+
let fileResourcesPending = false;
|
|
1498
|
+
try {
|
|
1499
|
+
if (invocationIndex < 0) return void await finishProject(state);
|
|
1500
|
+
if (!state.runtime) {
|
|
1501
|
+
state.runtime = (0, test_runner_namespaceObject.createProjectRuntime)({
|
|
1502
|
+
project,
|
|
1503
|
+
setup: project.setup,
|
|
1504
|
+
signal: options.signal,
|
|
1505
|
+
setupTimeoutMs: options.setupTimeoutMs,
|
|
1506
|
+
teardownTimeoutMs: options.teardownTimeoutMs
|
|
1507
|
+
});
|
|
1508
|
+
(0, external_node_events_namespaceObject.setMaxListeners)(Math.max(10, Math.min(prepared.invocations.length, project.maxConcurrentFiles ?? options.maxConcurrentFiles) + 1), state.runtime.signal);
|
|
1509
|
+
options.announceProject(prepared, index);
|
|
1510
|
+
await state.runtime.start();
|
|
1511
|
+
state.initializing = false;
|
|
1512
|
+
if (!state.runtime.canRun) {
|
|
1513
|
+
state.fatal = true;
|
|
1514
|
+
state.setupFailed = true;
|
|
1515
|
+
}
|
|
1516
|
+
wake?.();
|
|
1517
|
+
}
|
|
1518
|
+
if (stopped(state)) return;
|
|
1519
|
+
const invocation = prepared.invocations[invocationIndex];
|
|
1520
|
+
options.onProgress(prepared, ` [document ${invocationIndex + 1}/${prepared.invocations.length}] ${invocation.document.sourcePath}`);
|
|
1521
|
+
await executeDocumentInvocation({
|
|
1522
|
+
invocation,
|
|
1523
|
+
project,
|
|
1524
|
+
projectContext: state.runtime.context,
|
|
1525
|
+
runDir: options.runDir,
|
|
1526
|
+
signal: state.runtime.signal,
|
|
1527
|
+
rootSignal: options.signal,
|
|
1528
|
+
shouldBail: options.shouldBail,
|
|
1529
|
+
isProjectFatal: ()=>state.fatal,
|
|
1530
|
+
markProjectFatal: ()=>{
|
|
1531
|
+
if (state.fatal) return;
|
|
1532
|
+
state.fatal = true;
|
|
1533
|
+
state.runtime?.abort(new test_runner_namespaceObject.WorkflowError(`Project "${project.name}" shared execution environment is unavailable.`, {
|
|
1534
|
+
code: 'PROJECT_RESOURCE_ERROR'
|
|
1535
|
+
}));
|
|
1536
|
+
},
|
|
1537
|
+
addFailedCases: options.addFailedCases,
|
|
1538
|
+
onProgress: (message)=>options.onProgress(prepared, message),
|
|
1539
|
+
sinks: state,
|
|
1540
|
+
isolatedContext: void 0 !== project.fileSetup,
|
|
1541
|
+
isProjectResourceFailure: isSharedResourceFailure,
|
|
1542
|
+
setupTimeoutMs: options.setupTimeoutMs,
|
|
1543
|
+
teardownTimeoutMs: options.teardownTimeoutMs,
|
|
1544
|
+
onUnresolvedResources: ()=>{
|
|
1545
|
+
fileResourcesPending = true;
|
|
1546
|
+
unresolved();
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
state.fatal = true;
|
|
1551
|
+
fileResourcesPending = !!state.runtime && (0, test_runner_namespaceObject.getPendingScopeOperations)(state.runtime.signal).length > 0;
|
|
1552
|
+
options.onInfrastructureError(error);
|
|
1553
|
+
} finally{
|
|
1554
|
+
state.initializing = false;
|
|
1555
|
+
try {
|
|
1556
|
+
if (1 === state.active && (stopped(state) || state.cursor === prepared.invocations.length)) await finishProject(state);
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
options.onInfrastructureError(error);
|
|
1559
|
+
}
|
|
1560
|
+
state.active--;
|
|
1561
|
+
if (state.runtime && (fileResourcesPending || state.lifecycle?.cleanup?.status === 'pending')) (0, test_runner_namespaceObject.waitForScopeOperations)(state.runtime.signal).then(release, (error)=>{
|
|
1562
|
+
options.onInfrastructureError(error);
|
|
1563
|
+
release();
|
|
1564
|
+
});
|
|
1565
|
+
else release();
|
|
1566
|
+
wake?.();
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
while(true){
|
|
1570
|
+
const changed = new Promise((resolve)=>{
|
|
1571
|
+
wake = resolve;
|
|
1572
|
+
});
|
|
1573
|
+
while(occupied < options.maxConcurrentFiles){
|
|
1574
|
+
const job = admit();
|
|
1575
|
+
if (!job) break;
|
|
1576
|
+
const task = run(job);
|
|
1577
|
+
running.add(task);
|
|
1578
|
+
const settled = ()=>{
|
|
1579
|
+
running.delete(task);
|
|
1580
|
+
wake?.();
|
|
1581
|
+
};
|
|
1582
|
+
task.then(settled, (error)=>{
|
|
1583
|
+
options.onInfrastructureError(error);
|
|
1584
|
+
settled();
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
if (!running.size) break;
|
|
1588
|
+
await changed;
|
|
1589
|
+
}
|
|
1590
|
+
wake = void 0;
|
|
1591
|
+
await Promise.all(states.map(async (state)=>{
|
|
1592
|
+
if (!state.runtime || state.finished) return;
|
|
1593
|
+
const admission = new Promise((resolve)=>{
|
|
1594
|
+
cleanupQueue.push(resolve);
|
|
1595
|
+
});
|
|
1596
|
+
(0, test_runner_namespaceObject.trackResourceOperation)({}, admission, state.runtime.signal);
|
|
1597
|
+
while(occupied < options.maxConcurrentFiles && cleanupQueue.length){
|
|
1598
|
+
occupied++;
|
|
1599
|
+
cleanupQueue.shift()();
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
await finishProject(state);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
options.onInfrastructureError(error);
|
|
1605
|
+
} finally{
|
|
1606
|
+
Promise.all([
|
|
1607
|
+
admission,
|
|
1608
|
+
(0, test_runner_namespaceObject.getResourceCleanupCompletion)(state.runtime.signal)
|
|
1609
|
+
]).then(release, (error)=>{
|
|
1610
|
+
options.onInfrastructureError(error);
|
|
1611
|
+
release();
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}));
|
|
1615
|
+
for (const state of states){
|
|
1616
|
+
const { prepared } = state;
|
|
1617
|
+
if (!state.runtime) options.announceProject(prepared, states.indexOf(state));
|
|
1618
|
+
const completed = new Set(state.cases.map((item)=>item.caseId));
|
|
1619
|
+
const reason = invalid(state) ? 'project-preflight-failed' : state.setupFailed ? 'project-setup-failed' : options.signal.aborted ? 'interrupted' : state.fatal ? 'fatal-error' : 'bail';
|
|
1620
|
+
for (const document of prepared.documents)for (const item of document.cases)if (!completed.has(item.caseId)) state.cases.push({
|
|
1621
|
+
documentId: document.documentId,
|
|
1622
|
+
caseId: item.caseId,
|
|
1623
|
+
projectName: prepared.project.name,
|
|
1624
|
+
name: item.definition.name,
|
|
1625
|
+
sourcePath: item.sourcePath,
|
|
1626
|
+
caseIndex: item.caseIndex,
|
|
1627
|
+
status: 'not-run',
|
|
1628
|
+
notRunReason: reason
|
|
1629
|
+
});
|
|
1630
|
+
const order = new Map(prepared.documents.map((document, i)=>[
|
|
1631
|
+
document.documentId,
|
|
1632
|
+
i
|
|
1633
|
+
]));
|
|
1634
|
+
const position = (id)=>order.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
1635
|
+
state.cases.sort((a, b)=>position(a.documentId) - position(b.documentId) || a.caseIndex - b.caseIndex);
|
|
1636
|
+
state.documents.sort((a, b)=>position(a.documentId) - position(b.documentId));
|
|
1637
|
+
}
|
|
1638
|
+
return states;
|
|
1639
|
+
}
|
|
1304
1640
|
function createOperationJournal(runDir) {
|
|
1305
1641
|
const path = (0, external_node_path_namespaceObject.join)(runDir, 'operations.jsonl');
|
|
1306
1642
|
(0, external_node_fs_namespaceObject.writeFileSync)(path, '', {
|
|
@@ -1370,7 +1706,7 @@ async function runPreparedTestProject(plan) {
|
|
|
1370
1706
|
const totalCases = preparedProjects.reduce((total, prepared)=>total + prepared.selectedCaseCount, 0);
|
|
1371
1707
|
const totalErrors = preparedProjects.reduce((total, prepared)=>total + prepared.collectionErrors.length, 0);
|
|
1372
1708
|
progress(`midscene-test: preflighted ${preparedProjects.length} projects, ${totalDocuments} documents, ${totalCases} cases, ${totalErrors} collection errors`);
|
|
1373
|
-
const effectiveConcurrency = Math.min(definition.test.
|
|
1709
|
+
const effectiveConcurrency = Math.min(definition.test.maxConcurrentFiles ?? 1 / 0, Math.max(1, totalDocuments));
|
|
1374
1710
|
await writeTestRunPlan(runDir, preparedProjects);
|
|
1375
1711
|
const journal = createOperationJournal(runDir);
|
|
1376
1712
|
const rootController = new AbortController();
|
|
@@ -1384,6 +1720,18 @@ async function runPreparedTestProject(plan) {
|
|
|
1384
1720
|
process.on('SIGTERM', sigterm);
|
|
1385
1721
|
let failedCaseCount = 0;
|
|
1386
1722
|
const bailReached = ()=>definition.test.bail > 0 && failedCaseCount >= definition.test.bail;
|
|
1723
|
+
let shutdownDeadline;
|
|
1724
|
+
const beginShutdown = ()=>{
|
|
1725
|
+
shutdownDeadline ??= Date.now() + definition.test.teardownTimeout;
|
|
1726
|
+
};
|
|
1727
|
+
rootController.signal.addEventListener('abort', beginShutdown, {
|
|
1728
|
+
once: true
|
|
1729
|
+
});
|
|
1730
|
+
const addFailedCases = (count)=>{
|
|
1731
|
+
failedCaseCount += count;
|
|
1732
|
+
if (bailReached()) beginShutdown();
|
|
1733
|
+
};
|
|
1734
|
+
const teardownTimeoutMs = ()=>Math.max(0, Math.min(definition.test.teardownTimeout, void 0 === shutdownDeadline ? 1 / 0 : shutdownDeadline - Date.now()));
|
|
1387
1735
|
let hasInfrastructureError = false;
|
|
1388
1736
|
const infrastructureErrors = [];
|
|
1389
1737
|
const recordInfrastructureError = (error)=>{
|
|
@@ -1445,112 +1793,25 @@ async function runPreparedTestProject(plan) {
|
|
|
1445
1793
|
announceProject(prepared, projectIndex);
|
|
1446
1794
|
return buildProjectResult(prepared, notRunSuite(prepared, reason), []);
|
|
1447
1795
|
};
|
|
1448
|
-
const runPreparedProject = async (prepared, projectIndex)=>{
|
|
1449
|
-
const { project } = prepared;
|
|
1450
|
-
announceProject(prepared, projectIndex);
|
|
1451
|
-
const projectProgress = (message)=>runInfrastructureCallback(()=>progress(effectiveConcurrency > 1 ? `[${project.name}]${message}` : message));
|
|
1452
|
-
const cases = [];
|
|
1453
|
-
const documents = [];
|
|
1454
|
-
let lifecycle;
|
|
1455
|
-
let projectFatal = false;
|
|
1456
|
-
if (prepared.collectionErrors.length > 0 || 'run' === plan.preflightScope && totalErrors > 0) cases.push(...notRunSuite(prepared, 'project-preflight-failed'));
|
|
1457
|
-
else if (rootController.signal.aborted) cases.push(...notRunSuite(prepared, 'interrupted'));
|
|
1458
|
-
else if (bailReached()) cases.push(...notRunSuite(prepared, 'bail'));
|
|
1459
|
-
else {
|
|
1460
|
-
const runtime = (0, test_runner_namespaceObject.createProjectRuntime)({
|
|
1461
|
-
project,
|
|
1462
|
-
setup: project.setup,
|
|
1463
|
-
signal: rootController.signal
|
|
1464
|
-
});
|
|
1465
|
-
let hasProjectExecutionError = false;
|
|
1466
|
-
try {
|
|
1467
|
-
await runtime.start();
|
|
1468
|
-
if (runtime.canRun) {
|
|
1469
|
-
const runDocument = async (invocation, documentIndex)=>{
|
|
1470
|
-
const { document } = invocation;
|
|
1471
|
-
if (rootController.signal.aborted || bailReached() || projectFatal) {
|
|
1472
|
-
const reason = rootController.signal.aborted ? 'interrupted' : projectFatal ? 'fatal-error' : 'bail';
|
|
1473
|
-
cases.push(...document.cases.map((item)=>asNotRun(document.documentId, item, project.name, reason)));
|
|
1474
|
-
return;
|
|
1475
|
-
}
|
|
1476
|
-
projectProgress(` [document ${documentIndex + 1}/${prepared.documents.length}] ${document.sourcePath}`);
|
|
1477
|
-
await executeDocumentInvocation({
|
|
1478
|
-
invocation,
|
|
1479
|
-
project,
|
|
1480
|
-
projectContext: runtime.context,
|
|
1481
|
-
runDir,
|
|
1482
|
-
signal: runtime.signal,
|
|
1483
|
-
rootSignal: rootController.signal,
|
|
1484
|
-
shouldBail: bailReached,
|
|
1485
|
-
isProjectFatal: ()=>projectFatal,
|
|
1486
|
-
markProjectFatal: ()=>{
|
|
1487
|
-
projectFatal = true;
|
|
1488
|
-
},
|
|
1489
|
-
addFailedCases: (count)=>{
|
|
1490
|
-
failedCaseCount += count;
|
|
1491
|
-
},
|
|
1492
|
-
onProgress: projectProgress,
|
|
1493
|
-
sinks: {
|
|
1494
|
-
cases,
|
|
1495
|
-
documents
|
|
1496
|
-
}
|
|
1497
|
-
});
|
|
1498
|
-
};
|
|
1499
|
-
let prerequisiteFailed = false;
|
|
1500
|
-
const prerequisite = prepared.invocations.find((invocation)=>invocation.document.documentId === prepared.prerequisiteDocumentId);
|
|
1501
|
-
const prerequisiteCount = prerequisite ? 1 : 0;
|
|
1502
|
-
if (prerequisite) {
|
|
1503
|
-
await runDocument(prerequisite, 0);
|
|
1504
|
-
prerequisiteFailed = documents.at(-1)?.status === 'failed' || latestById(cases, (item)=>item.caseId).some((item)=>'success' !== item.status);
|
|
1505
|
-
}
|
|
1506
|
-
if (!prerequisiteFailed) await (0, test_runner_namespaceObject.runConcurrentJobs)(prepared.invocations.filter((invocation)=>invocation !== prerequisite), {
|
|
1507
|
-
concurrency: prepared.documentConcurrency,
|
|
1508
|
-
shouldStop: ()=>rootController.signal.aborted || bailReached() || projectFatal
|
|
1509
|
-
}, async (invocation, index)=>{
|
|
1510
|
-
try {
|
|
1511
|
-
await runDocument(invocation, index + prerequisiteCount);
|
|
1512
|
-
} catch (error) {
|
|
1513
|
-
recordInfrastructureError(error);
|
|
1514
|
-
throw error;
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
const completed = new Set(cases.map((item)=>item.caseId));
|
|
1518
|
-
const reason = prerequisiteFailed ? 'project-setup-failed' : rootController.signal.aborted ? 'interrupted' : projectFatal ? 'fatal-error' : 'bail';
|
|
1519
|
-
cases.push(...notRunSuite(prepared, reason).filter((item)=>!completed.has(item.caseId)));
|
|
1520
|
-
} else cases.push(...notRunSuite(prepared, rootController.signal.aborted ? 'interrupted' : 'project-setup-failed'));
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
hasProjectExecutionError = true;
|
|
1523
|
-
recordInfrastructureError(error);
|
|
1524
|
-
} finally{
|
|
1525
|
-
const hasFailure = hasProjectExecutionError || latestById(cases, (item)=>item.caseId).some((item)=>'success' !== item.status) || latestById(documents, (item)=>item.documentId).some((item)=>'failed' === item.status) || projectFatal || rootController.signal.aborted;
|
|
1526
|
-
lifecycle = await runtime.finish(hasFailure ? 'failed' : 'success');
|
|
1527
|
-
}
|
|
1528
|
-
if (hasProjectExecutionError) {
|
|
1529
|
-
const completed = new Set(cases.map((item)=>item.caseId));
|
|
1530
|
-
cases.push(...notRunSuite(prepared, 'interrupted').filter((item)=>!completed.has(item.caseId)));
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
const order = new Map(prepared.documents.map((document, index)=>[
|
|
1534
|
-
document.documentId,
|
|
1535
|
-
index
|
|
1536
|
-
]));
|
|
1537
|
-
const documentOrder = (id)=>order.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
1538
|
-
cases.sort((a, b)=>documentOrder(a.documentId) - documentOrder(b.documentId) || a.caseIndex - b.caseIndex);
|
|
1539
|
-
documents.sort((a, b)=>documentOrder(a.documentId) - documentOrder(b.documentId) || (a.attemptIndex ?? 0) - (b.attemptIndex ?? 0));
|
|
1540
|
-
return buildProjectResult(prepared, cases, documents, lifecycle);
|
|
1541
|
-
};
|
|
1542
1796
|
const projectResults = new Array(preparedProjects.length);
|
|
1543
1797
|
try {
|
|
1544
|
-
await (
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1798
|
+
const states = await runPreparedFiles({
|
|
1799
|
+
projects: preparedProjects,
|
|
1800
|
+
maxConcurrentFiles: effectiveConcurrency,
|
|
1801
|
+
maxConcurrentProjects: definition.test.maxConcurrentProjects ?? 1 / 0,
|
|
1802
|
+
setupTimeoutMs: definition.test.setupTimeout,
|
|
1803
|
+
teardownTimeoutMs,
|
|
1804
|
+
signal: rootController.signal,
|
|
1805
|
+
runDir,
|
|
1806
|
+
preflightFailed: 'run' === plan.preflightScope && totalErrors > 0,
|
|
1807
|
+
shouldBail: bailReached,
|
|
1808
|
+
addFailedCases,
|
|
1809
|
+
announceProject,
|
|
1810
|
+
onProgress: (prepared, message)=>runInfrastructureCallback(()=>progress(effectiveConcurrency > 1 ? `[${prepared.project.name}]${message}` : message)),
|
|
1811
|
+
onInfrastructureError: recordInfrastructureError
|
|
1812
|
+
});
|
|
1813
|
+
states.forEach((state, index)=>{
|
|
1814
|
+
projectResults[index] = buildProjectResult(state.prepared, state.cases, state.documents, state.lifecycle);
|
|
1554
1815
|
});
|
|
1555
1816
|
} catch (error) {
|
|
1556
1817
|
recordInfrastructureError(error);
|
|
@@ -1659,6 +1920,7 @@ async function runTestProject(options = {}) {
|
|
|
1659
1920
|
}
|
|
1660
1921
|
exports.DEFAULT_TEST_FILE_SELECTION = __webpack_exports__.DEFAULT_TEST_FILE_SELECTION;
|
|
1661
1922
|
exports.createTestRunId = __webpack_exports__.createTestRunId;
|
|
1923
|
+
exports.defineFileSetup = __webpack_exports__.defineFileSetup;
|
|
1662
1924
|
exports.defineProjectSetup = __webpack_exports__.defineProjectSetup;
|
|
1663
1925
|
exports.defineTestProject = __webpack_exports__.defineTestProject;
|
|
1664
1926
|
exports.discoverTestConfig = __webpack_exports__.discoverTestConfig;
|
|
@@ -1669,6 +1931,7 @@ exports.validateTestFileSelection = __webpack_exports__.validateTestFileSelectio
|
|
|
1669
1931
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
1670
1932
|
"DEFAULT_TEST_FILE_SELECTION",
|
|
1671
1933
|
"createTestRunId",
|
|
1934
|
+
"defineFileSetup",
|
|
1672
1935
|
"defineProjectSetup",
|
|
1673
1936
|
"defineTestProject",
|
|
1674
1937
|
"discoverTestConfig",
|