@midscene/test 1.16.1 → 1.16.5
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/{1~77.mjs → 1~731.mjs} +9 -9
- package/dist/es/1~731.mjs.map +1 -0
- package/dist/es/cli/index.mjs +342 -83
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/index.mjs +45 -1
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +1532 -134
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/cli/index.js +340 -81
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/index.js +45 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/test-cli.js +1523 -131
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/templates/test/android/.env.example +1 -1
- package/dist/templates/test/android/README.md +1 -1
- package/dist/templates/test/android/README.zh.md +1 -1
- package/dist/templates/test/computer/.env.example +1 -1
- package/dist/templates/test/computer/README.md +1 -1
- package/dist/templates/test/computer/README.zh.md +1 -1
- package/dist/templates/test/harmony/.env.example +1 -1
- package/dist/templates/test/harmony/README.md +1 -1
- package/dist/templates/test/harmony/README.zh.md +1 -1
- package/dist/templates/test/ios/.env.example +1 -1
- package/dist/templates/test/ios/README.md +1 -1
- package/dist/templates/test/ios/README.zh.md +1 -1
- package/dist/templates/test/web/.env.example +1 -1
- package/dist/templates/test/web/README.md +1 -1
- package/dist/templates/test/web/README.zh.md +1 -1
- package/dist/types/cli/index.d.ts +75 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +14 -13
- package/dist/es/1~77.mjs.map +0 -1
package/dist/lib/cli/index.js
CHANGED
|
@@ -393,11 +393,13 @@ function parseWorkflowSource(source) {
|
|
|
393
393
|
start: state.position + sourceOffset,
|
|
394
394
|
end: 0,
|
|
395
395
|
value: null,
|
|
396
|
-
children: []
|
|
396
|
+
children: [],
|
|
397
|
+
kind: null
|
|
397
398
|
});
|
|
398
399
|
const node = stack.pop();
|
|
399
400
|
node.end = state.position + sourceOffset;
|
|
400
401
|
node.value = state.result;
|
|
402
|
+
node.kind = state.kind;
|
|
401
403
|
stack.at(-1)?.children.push(node);
|
|
402
404
|
nodes.push(node);
|
|
403
405
|
if ('mapping' !== state.kind) return;
|
|
@@ -427,6 +429,36 @@ function parseWorkflowSource(source) {
|
|
|
427
429
|
const value = (0, test_runner_namespaceObject.sanitizeReportText)(redacted, 65536);
|
|
428
430
|
return {
|
|
429
431
|
parsed,
|
|
432
|
+
locate (path) {
|
|
433
|
+
let node = nodes.at(-1);
|
|
434
|
+
for (const part of path){
|
|
435
|
+
if (!node) return;
|
|
436
|
+
while(1 === node.children.length && node.children[0].value === node.value)node = node.children[0];
|
|
437
|
+
if ('number' == typeof part && 'sequence' === node.kind) node = node.children[part];
|
|
438
|
+
else {
|
|
439
|
+
if ('string' != typeof part || 'mapping' !== node.kind) return;
|
|
440
|
+
const children = node.children;
|
|
441
|
+
node = void 0;
|
|
442
|
+
for(let index = 0; index < children.length; index += 2)if (children[index].value === part) {
|
|
443
|
+
node = children[index + 1];
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!node) return;
|
|
449
|
+
let start = node.start;
|
|
450
|
+
while(start < node.end)if (/\s/.test(source[start])) start++;
|
|
451
|
+
else if ('#' === source[start]) {
|
|
452
|
+
const end = source.indexOf('\n', start);
|
|
453
|
+
if (end < 0) return;
|
|
454
|
+
start = end + 1;
|
|
455
|
+
} else break;
|
|
456
|
+
const prefix = source.slice(0, start).split(/\r\n|\r|\n/);
|
|
457
|
+
return {
|
|
458
|
+
line: prefix.length,
|
|
459
|
+
column: prefix.at(-1).length + 1
|
|
460
|
+
};
|
|
461
|
+
},
|
|
430
462
|
sourceYaml: {
|
|
431
463
|
value,
|
|
432
464
|
...redactedPaths.size ? {
|
|
@@ -539,6 +571,18 @@ function collectWorkflowDocument(source, options) {
|
|
|
539
571
|
const sourcePath = source.sourcePath.split(external_node_path_namespaceObject.sep).join('/');
|
|
540
572
|
const resolveStepVariables = (step, phase, stepIndex, caseIndex)=>({
|
|
541
573
|
...step,
|
|
574
|
+
meta: {
|
|
575
|
+
...step.meta,
|
|
576
|
+
sourceLocation: snapshot.locate('steps' === phase ? [
|
|
577
|
+
'cases',
|
|
578
|
+
caseIndex,
|
|
579
|
+
'steps',
|
|
580
|
+
stepIndex
|
|
581
|
+
] : [
|
|
582
|
+
phase,
|
|
583
|
+
stepIndex
|
|
584
|
+
])
|
|
585
|
+
},
|
|
542
586
|
templateInput: step.input,
|
|
543
587
|
input: resolveWorkflowVariables(step.input, {
|
|
544
588
|
variables: options.variables,
|
|
@@ -714,6 +758,9 @@ const writeTestProjectRunResult = (options)=>{
|
|
|
714
758
|
markdown: relativeToSummary(result.summaryPath, result.markdownPath)
|
|
715
759
|
} : {},
|
|
716
760
|
summary: result.summary,
|
|
761
|
+
...result.metrics ? {
|
|
762
|
+
metrics: result.metrics
|
|
763
|
+
} : {},
|
|
717
764
|
projects: result.projects.map((project)=>({
|
|
718
765
|
projectId: project.projectId,
|
|
719
766
|
name: project.name,
|
|
@@ -1115,7 +1162,8 @@ async function prepareTestRun(options) {
|
|
|
1115
1162
|
preflightScope: hasFormatMismatch ? 'run' : 'project',
|
|
1116
1163
|
reportEnabled: true,
|
|
1117
1164
|
publications: [],
|
|
1118
|
-
onProgress: options.onProgress ?? (()=>{})
|
|
1165
|
+
onProgress: options.onProgress ?? (()=>{}),
|
|
1166
|
+
onEvent: options.onEvent
|
|
1119
1167
|
};
|
|
1120
1168
|
}
|
|
1121
1169
|
const external_node_events_namespaceObject = require("node:events");
|
|
@@ -1196,6 +1244,38 @@ async function writeTestResultMarkdown(result, index = emptyIndex()) {
|
|
|
1196
1244
|
}));
|
|
1197
1245
|
return output;
|
|
1198
1246
|
}
|
|
1247
|
+
function scopeNodeConsole(node) {
|
|
1248
|
+
return {
|
|
1249
|
+
...node,
|
|
1250
|
+
execute (ctx) {
|
|
1251
|
+
const position = 'case' === ctx.scope ? ctx.case : ctx.document;
|
|
1252
|
+
const scope = {
|
|
1253
|
+
projectName: position.projectName,
|
|
1254
|
+
sourcePath: position.sourcePath,
|
|
1255
|
+
...'case' === ctx.scope ? {
|
|
1256
|
+
caseId: ctx.case.caseId,
|
|
1257
|
+
caseName: ctx.case.name,
|
|
1258
|
+
runId: ctx.case.runId
|
|
1259
|
+
} : {
|
|
1260
|
+
runId: ctx.document.documentRunId
|
|
1261
|
+
},
|
|
1262
|
+
phase: position.phase,
|
|
1263
|
+
stepIndex: position.stepIndex,
|
|
1264
|
+
node: node.name
|
|
1265
|
+
};
|
|
1266
|
+
return (0, logger_namespaceObject.runWithConsoleLogScope)(scope, ()=>node.execute({
|
|
1267
|
+
...ctx,
|
|
1268
|
+
onTeardown (teardown) {
|
|
1269
|
+
ctx.onTeardown(()=>(0, logger_namespaceObject.runWithConsoleLogScope)({
|
|
1270
|
+
...scope,
|
|
1271
|
+
phase: 'teardown',
|
|
1272
|
+
stepIndex: void 0
|
|
1273
|
+
}, teardown));
|
|
1274
|
+
}
|
|
1275
|
+
}));
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1199
1279
|
const stepPosition = (info)=>'case' === info.scope ? info.case : info.document;
|
|
1200
1280
|
const formatStep = (info)=>{
|
|
1201
1281
|
const position = stepPosition(info);
|
|
@@ -1242,69 +1322,121 @@ async function executeDocumentInvocation(options) {
|
|
|
1242
1322
|
const documentRetry = 'document' === invocation.retry.scope;
|
|
1243
1323
|
const attempts = [];
|
|
1244
1324
|
const fileSetup = invocation.bindings.documentSetup;
|
|
1245
|
-
const documentSetup =
|
|
1325
|
+
const documentSetup = fileSetup ? {
|
|
1246
1326
|
...fileSetup,
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1327
|
+
setup (ctx) {
|
|
1328
|
+
const scope = {
|
|
1329
|
+
projectName: project.name,
|
|
1330
|
+
sourcePath: document.sourcePath,
|
|
1331
|
+
runId: ctx.document.documentRunId,
|
|
1332
|
+
phase: 'fileSetup'
|
|
1333
|
+
};
|
|
1334
|
+
return (0, logger_namespaceObject.runWithConsoleLogScope)(scope, async ()=>{
|
|
1335
|
+
try {
|
|
1336
|
+
return await fileSetup.setup({
|
|
1337
|
+
...ctx,
|
|
1338
|
+
onTeardown (teardown) {
|
|
1339
|
+
ctx.onTeardown(()=>(0, logger_namespaceObject.runWithConsoleLogScope)({
|
|
1340
|
+
...scope,
|
|
1341
|
+
phase: 'teardown'
|
|
1342
|
+
}, teardown));
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
} catch (error) {
|
|
1346
|
+
if (isProjectResourceFailure(error)) options.markProjectFatal();
|
|
1347
|
+
throw error;
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1254
1350
|
}
|
|
1255
|
-
} :
|
|
1351
|
+
} : void 0;
|
|
1352
|
+
options.onEvent?.({
|
|
1353
|
+
type: 'document-start',
|
|
1354
|
+
documentId: document.documentId,
|
|
1355
|
+
projectName: project.name,
|
|
1356
|
+
sourcePath: document.sourcePath,
|
|
1357
|
+
cases: document.cases.length
|
|
1358
|
+
});
|
|
1256
1359
|
await (0, test_runner_namespaceObject.runDocumentAttempts)({
|
|
1257
1360
|
retry: documentRetry ? invocation.retry.count : 0,
|
|
1258
1361
|
signal: options.signal,
|
|
1259
1362
|
shouldStop: ()=>options.shouldBail() || options.isProjectFatal()
|
|
1260
1363
|
}, async (documentAttemptIndex)=>{
|
|
1261
1364
|
if (documentRetry) options.onProgress(` file attempt ${documentAttemptIndex + 1}/${invocation.retry.count + 1}: ${document.sourcePath}`);
|
|
1262
|
-
const execution = await (0,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1365
|
+
const execution = await (0, logger_namespaceObject.runWithConsoleLogScope)({
|
|
1366
|
+
projectName: project.name,
|
|
1367
|
+
sourcePath: document.sourcePath
|
|
1368
|
+
}, ()=>(0, test_runner_namespaceObject.runWorkflowDocument)(document, {
|
|
1369
|
+
plan: invocation.plan,
|
|
1370
|
+
documentAttemptIndex: documentRetry ? documentAttemptIndex : void 0,
|
|
1371
|
+
...invocation.bindings,
|
|
1372
|
+
resolveNode: (name)=>scopeNodeConsole(invocation.bindings.resolveNode(name)),
|
|
1373
|
+
documentSetup,
|
|
1374
|
+
setupTimeoutMs: options.setupTimeoutMs,
|
|
1375
|
+
teardownTimeoutMs: options.teardownTimeoutMs,
|
|
1376
|
+
project,
|
|
1377
|
+
projectContext: options.projectContext,
|
|
1378
|
+
retry: documentRetry ? 0 : invocation.retry.count,
|
|
1379
|
+
signal: options.signal,
|
|
1380
|
+
defaultTimeoutMs: invocation.defaultTimeoutMs,
|
|
1381
|
+
shouldStop: ()=>options.rootSignal.aborted || options.shouldBail() || options.isProjectFatal(),
|
|
1382
|
+
stopReason: ()=>options.rootSignal.aborted ? 'interrupted' : options.isProjectFatal() ? 'fatal-error' : 'bail',
|
|
1383
|
+
isFatalError: (run)=>[
|
|
1384
|
+
...run.beforeEach,
|
|
1385
|
+
...run.steps,
|
|
1386
|
+
...run.afterEach
|
|
1387
|
+
].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)),
|
|
1388
|
+
onCaseStart: (collectedCase)=>{
|
|
1389
|
+
options.onProgress(` [case ${collectedCase.caseIndex + 1}/${document.cases.length}] ${collectedCase.definition.name}`);
|
|
1390
|
+
},
|
|
1391
|
+
onStepStart: (info)=>{
|
|
1392
|
+
options.onEvent?.({
|
|
1393
|
+
type: 'step-start',
|
|
1394
|
+
projectName: project.name,
|
|
1395
|
+
info
|
|
1396
|
+
});
|
|
1397
|
+
const indent = 'case' === info.scope ? ' ' : ' ';
|
|
1398
|
+
options.onProgress(`${indent}→ ${formatStep(info)}`);
|
|
1399
|
+
},
|
|
1400
|
+
onStepResult: async (info, result)=>{
|
|
1401
|
+
if (isProjectResourceFailure(result.error)) options.markProjectFatal();
|
|
1402
|
+
await invocation.bindings.onStepResult?.(info, result);
|
|
1403
|
+
options.onProgress(formatStepResult(info, result));
|
|
1404
|
+
options.onEvent?.({
|
|
1405
|
+
type: 'step-end',
|
|
1406
|
+
projectName: project.name,
|
|
1407
|
+
info,
|
|
1408
|
+
result
|
|
1409
|
+
});
|
|
1410
|
+
},
|
|
1411
|
+
onCaseResult: async (attempt)=>{
|
|
1412
|
+
await writeCaseAttemptResult(options.runDir, project.projectId, document.documentId, attempt);
|
|
1413
|
+
options.onProgress(` ${'success' === attempt.status ? '✓' : '✗'} case attempt ${attempt.attemptIndex + 1}/${documentRetry ? 1 : invocation.retry.count + 1}: ${attempt.name} (${attempt.durationMs} ms)`);
|
|
1414
|
+
if (!documentRetry) options.onEvent?.({
|
|
1415
|
+
type: 'case-attempt-end',
|
|
1416
|
+
attempt,
|
|
1417
|
+
maxAttempts: invocation.retry.count + 1
|
|
1418
|
+
});
|
|
1419
|
+
},
|
|
1420
|
+
onCaseOutcome: (outcome)=>{
|
|
1421
|
+
if (documentRetry) options.onEvent?.({
|
|
1422
|
+
type: 'case-progress',
|
|
1423
|
+
outcome,
|
|
1424
|
+
documentAttemptIndex
|
|
1425
|
+
});
|
|
1426
|
+
else options.onEvent?.({
|
|
1427
|
+
type: 'case-end',
|
|
1428
|
+
outcome
|
|
1429
|
+
});
|
|
1430
|
+
if (outcome.attempts?.some((attempt)=>[
|
|
1431
|
+
...attempt.beforeEach,
|
|
1432
|
+
...attempt.steps,
|
|
1433
|
+
...attempt.afterEach
|
|
1434
|
+
].some((step)=>isProjectResourceFailure(step.error)) || attempt.teardownErrors?.some(isProjectResourceFailure))) options.markProjectFatal();
|
|
1435
|
+
if (!options.isolatedContext && caseHasFatalError(outcome)) options.markProjectFatal();
|
|
1436
|
+
if (!documentRetry && 'failed' === outcome.status) options.addFailedCases(1);
|
|
1437
|
+
},
|
|
1438
|
+
onDocumentResult: options.onDocumentResult
|
|
1439
|
+
})).catch(async (error)=>{
|
|
1308
1440
|
if (error instanceof test_runner_namespaceObject.WorkflowExecutionFailure) {
|
|
1309
1441
|
const partial = error.result;
|
|
1310
1442
|
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)));
|
|
@@ -1374,6 +1506,11 @@ async function executeDocumentInvocation(options) {
|
|
|
1374
1506
|
shouldStop: options.shouldBail()
|
|
1375
1507
|
});
|
|
1376
1508
|
if (documentRetry) execution.document.retryDecision = decision;
|
|
1509
|
+
if (documentRetry && 'retry' === decision.decision) options.onEvent?.({
|
|
1510
|
+
type: 'document-retry',
|
|
1511
|
+
document: execution.document,
|
|
1512
|
+
maxAttempts: invocation.retry.count + 1
|
|
1513
|
+
});
|
|
1377
1514
|
attempts.push(execution);
|
|
1378
1515
|
sinks.cases.push(...execution.cases.map((outcome)=>({
|
|
1379
1516
|
...outcome,
|
|
@@ -1408,7 +1545,18 @@ async function executeDocumentInvocation(options) {
|
|
|
1408
1545
|
});
|
|
1409
1546
|
const final = attempts.at(-1);
|
|
1410
1547
|
if (!final) return;
|
|
1548
|
+
if (documentRetry) for (const outcome of final.cases)options.onEvent?.({
|
|
1549
|
+
type: 'case-end',
|
|
1550
|
+
outcome,
|
|
1551
|
+
documentRetries: attempts.length - 1
|
|
1552
|
+
});
|
|
1411
1553
|
const failedCases = final.cases.filter((item)=>'failed' === item.status).length;
|
|
1554
|
+
options.onEvent?.({
|
|
1555
|
+
type: 'document-end',
|
|
1556
|
+
document: final.document,
|
|
1557
|
+
cases: final.cases,
|
|
1558
|
+
durationMs: attempts.reduce((total, attempt)=>total + attempt.document.durationMs, 0)
|
|
1559
|
+
});
|
|
1412
1560
|
if (documentRetry && failedCases) options.addFailedCases(failedCases);
|
|
1413
1561
|
else if ('failed' === final.document.status && 0 === failedCases) options.addFailedCases(Math.max(1, failedCases));
|
|
1414
1562
|
}
|
|
@@ -1448,11 +1596,45 @@ async function runPreparedFiles(options) {
|
|
|
1448
1596
|
code: 'UNRESOLVED_FILE_RESOURCES'
|
|
1449
1597
|
}));
|
|
1450
1598
|
};
|
|
1599
|
+
const finalizeProject = (state)=>{
|
|
1600
|
+
const { prepared } = state;
|
|
1601
|
+
const completed = new Set(state.cases.map((item)=>item.caseId));
|
|
1602
|
+
const reason = invalid(state) ? 'project-preflight-failed' : state.setupFailed ? 'project-setup-failed' : options.signal.aborted ? 'interrupted' : state.fatal ? 'fatal-error' : 'bail';
|
|
1603
|
+
for (const document of prepared.documents)for (const item of document.cases)if (!completed.has(item.caseId)) state.cases.push({
|
|
1604
|
+
documentId: document.documentId,
|
|
1605
|
+
caseId: item.caseId,
|
|
1606
|
+
projectName: prepared.project.name,
|
|
1607
|
+
name: item.definition.name,
|
|
1608
|
+
sourcePath: item.sourcePath,
|
|
1609
|
+
caseIndex: item.caseIndex,
|
|
1610
|
+
status: 'not-run',
|
|
1611
|
+
notRunReason: reason
|
|
1612
|
+
});
|
|
1613
|
+
const order = new Map(prepared.documents.map((document, i)=>[
|
|
1614
|
+
document.documentId,
|
|
1615
|
+
i
|
|
1616
|
+
]));
|
|
1617
|
+
const position = (id)=>order.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
1618
|
+
state.cases.sort((a, b)=>position(a.documentId) - position(b.documentId) || a.caseIndex - b.caseIndex);
|
|
1619
|
+
state.documents.sort((a, b)=>position(a.documentId) - position(b.documentId));
|
|
1620
|
+
};
|
|
1451
1621
|
const finishProject = async (state)=>{
|
|
1452
1622
|
if (!state.runtime || state.finished) return;
|
|
1453
1623
|
state.finished = true;
|
|
1624
|
+
try {
|
|
1625
|
+
options.onEvent?.({
|
|
1626
|
+
type: 'project-phase',
|
|
1627
|
+
projectName: state.prepared.project.name,
|
|
1628
|
+
phase: 'teardown'
|
|
1629
|
+
});
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
options.onInfrastructureError(error);
|
|
1632
|
+
}
|
|
1454
1633
|
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
|
|
1634
|
+
state.lifecycle = await (0, logger_namespaceObject.runWithConsoleLogScope)({
|
|
1635
|
+
projectName: state.prepared.project.name,
|
|
1636
|
+
phase: 'teardown'
|
|
1637
|
+
}, ()=>state.runtime.finish(failed ? 'failed' : 'success'));
|
|
1456
1638
|
const releaseProject = ()=>{
|
|
1457
1639
|
occupiedProjects--;
|
|
1458
1640
|
wake?.();
|
|
@@ -1466,7 +1648,11 @@ async function runPreparedFiles(options) {
|
|
|
1466
1648
|
options.onInfrastructureError(error);
|
|
1467
1649
|
releaseProject();
|
|
1468
1650
|
});
|
|
1469
|
-
} else
|
|
1651
|
+
} else {
|
|
1652
|
+
releaseProject();
|
|
1653
|
+
finalizeProject(state);
|
|
1654
|
+
options.onProjectEnd?.(state);
|
|
1655
|
+
}
|
|
1470
1656
|
};
|
|
1471
1657
|
const admit = ()=>{
|
|
1472
1658
|
for(let offset = 0; offset < states.length; offset++){
|
|
@@ -1507,7 +1693,20 @@ async function runPreparedFiles(options) {
|
|
|
1507
1693
|
});
|
|
1508
1694
|
(0, external_node_events_namespaceObject.setMaxListeners)(Math.max(10, Math.min(prepared.invocations.length, project.maxConcurrentFiles ?? options.maxConcurrentFiles) + 1), state.runtime.signal);
|
|
1509
1695
|
options.announceProject(prepared, index);
|
|
1510
|
-
|
|
1696
|
+
options.onEvent?.({
|
|
1697
|
+
type: 'project-phase',
|
|
1698
|
+
projectName: project.name,
|
|
1699
|
+
phase: 'setup'
|
|
1700
|
+
});
|
|
1701
|
+
await (0, logger_namespaceObject.runWithConsoleLogScope)({
|
|
1702
|
+
projectName: project.name,
|
|
1703
|
+
phase: 'setup'
|
|
1704
|
+
}, ()=>state.runtime.start());
|
|
1705
|
+
options.onEvent?.({
|
|
1706
|
+
type: 'project-phase',
|
|
1707
|
+
projectName: project.name,
|
|
1708
|
+
phase: 'running'
|
|
1709
|
+
});
|
|
1511
1710
|
state.initializing = false;
|
|
1512
1711
|
if (!state.runtime.canRun) {
|
|
1513
1712
|
state.fatal = true;
|
|
@@ -1535,8 +1734,9 @@ async function runPreparedFiles(options) {
|
|
|
1535
1734
|
}));
|
|
1536
1735
|
},
|
|
1537
1736
|
addFailedCases: options.addFailedCases,
|
|
1538
|
-
onProgress: (message)=>options.onProgress(prepared, message),
|
|
1737
|
+
onProgress: (message)=>options.onProgress(prepared, options.maxConcurrentFiles > 1 ? ` [${invocation.document.sourcePath}]${message}` : message),
|
|
1539
1738
|
sinks: state,
|
|
1739
|
+
onEvent: options.onEvent,
|
|
1540
1740
|
isolatedContext: void 0 !== project.fileSetup,
|
|
1541
1741
|
isProjectResourceFailure: isSharedResourceFailure,
|
|
1542
1742
|
setupTimeoutMs: options.setupTimeoutMs,
|
|
@@ -1615,28 +1815,42 @@ async function runPreparedFiles(options) {
|
|
|
1615
1815
|
for (const state of states){
|
|
1616
1816
|
const { prepared } = state;
|
|
1617
1817
|
if (!state.runtime) options.announceProject(prepared, states.indexOf(state));
|
|
1618
|
-
|
|
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));
|
|
1818
|
+
finalizeProject(state);
|
|
1637
1819
|
}
|
|
1638
1820
|
return states;
|
|
1639
1821
|
}
|
|
1822
|
+
require("node:util");
|
|
1823
|
+
const env_namespaceObject = require("@midscene/shared/env");
|
|
1824
|
+
function configuredModelNames() {
|
|
1825
|
+
const env = env_namespaceObject.globalConfigManager.getAllEnvConfig();
|
|
1826
|
+
const defaultName = env.MIDSCENE_MODEL_NAME;
|
|
1827
|
+
const models = new Map();
|
|
1828
|
+
for (const [intent, name] of [
|
|
1829
|
+
[
|
|
1830
|
+
'default',
|
|
1831
|
+
defaultName
|
|
1832
|
+
],
|
|
1833
|
+
[
|
|
1834
|
+
'planning',
|
|
1835
|
+
env.MIDSCENE_PLANNING_MODEL_NAME || defaultName
|
|
1836
|
+
],
|
|
1837
|
+
[
|
|
1838
|
+
'insight',
|
|
1839
|
+
env.MIDSCENE_INSIGHT_MODEL_NAME || defaultName
|
|
1840
|
+
]
|
|
1841
|
+
]){
|
|
1842
|
+
if (!name) continue;
|
|
1843
|
+
const intents = models.get(name) ?? [];
|
|
1844
|
+
intents.push(intent);
|
|
1845
|
+
models.set(name, intents);
|
|
1846
|
+
}
|
|
1847
|
+
return [
|
|
1848
|
+
...models
|
|
1849
|
+
].map(([name, intents])=>({
|
|
1850
|
+
name,
|
|
1851
|
+
intents
|
|
1852
|
+
}));
|
|
1853
|
+
}
|
|
1640
1854
|
function createOperationJournal(runDir) {
|
|
1641
1855
|
const path = (0, external_node_path_namespaceObject.join)(runDir, 'operations.jsonl');
|
|
1642
1856
|
(0, external_node_fs_namespaceObject.writeFileSync)(path, '', {
|
|
@@ -1705,6 +1919,32 @@ async function runPreparedTestProject(plan) {
|
|
|
1705
1919
|
const totalDocuments = preparedProjects.reduce((total, prepared)=>total + prepared.documents.length, 0);
|
|
1706
1920
|
const totalCases = preparedProjects.reduce((total, prepared)=>total + prepared.selectedCaseCount, 0);
|
|
1707
1921
|
const totalErrors = preparedProjects.reduce((total, prepared)=>total + prepared.collectionErrors.length, 0);
|
|
1922
|
+
plan.onEvent?.({
|
|
1923
|
+
type: 'run-start',
|
|
1924
|
+
configuredModels: configuredModelNames(),
|
|
1925
|
+
projects: preparedProjects.length,
|
|
1926
|
+
documents: totalDocuments + totalErrors,
|
|
1927
|
+
cases: totalCases,
|
|
1928
|
+
projectRoot,
|
|
1929
|
+
configPath,
|
|
1930
|
+
startedAt: startedAt.toISOString(),
|
|
1931
|
+
maxConcurrentProjects: definition.test.maxConcurrentProjects,
|
|
1932
|
+
maxConcurrentFiles: definition.test.maxConcurrentFiles,
|
|
1933
|
+
files: preparedProjects.flatMap((prepared)=>[
|
|
1934
|
+
...prepared.documents.map((document)=>({
|
|
1935
|
+
documentId: document.documentId,
|
|
1936
|
+
projectName: prepared.project.name,
|
|
1937
|
+
sourcePath: document.sourcePath,
|
|
1938
|
+
cases: document.cases.length
|
|
1939
|
+
})),
|
|
1940
|
+
...prepared.collectionErrors.map((error)=>({
|
|
1941
|
+
documentId: error.sourcePath,
|
|
1942
|
+
projectName: prepared.project.name,
|
|
1943
|
+
sourcePath: error.sourcePath,
|
|
1944
|
+
cases: 0
|
|
1945
|
+
}))
|
|
1946
|
+
])
|
|
1947
|
+
});
|
|
1708
1948
|
progress(`midscene-test: preflighted ${preparedProjects.length} projects, ${totalDocuments} documents, ${totalCases} cases, ${totalErrors} collection errors`);
|
|
1709
1949
|
const effectiveConcurrency = Math.min(definition.test.maxConcurrentFiles ?? 1 / 0, Math.max(1, totalDocuments));
|
|
1710
1950
|
await writeTestRunPlan(runDir, preparedProjects);
|
|
@@ -1807,7 +2047,12 @@ async function runPreparedTestProject(plan) {
|
|
|
1807
2047
|
shouldBail: bailReached,
|
|
1808
2048
|
addFailedCases,
|
|
1809
2049
|
announceProject,
|
|
1810
|
-
onProgress: (prepared, message)=>runInfrastructureCallback(()=>progress(effectiveConcurrency > 1 ? `[${prepared.project.name}]${message}` : message)),
|
|
2050
|
+
onProgress: (prepared, message)=>runInfrastructureCallback(()=>progress(preparedProjects.length > 1 || effectiveConcurrency > 1 ? `[${prepared.project.name}]${message}` : message)),
|
|
2051
|
+
onEvent: (event)=>runInfrastructureCallback(()=>plan.onEvent?.(event)),
|
|
2052
|
+
onProjectEnd: (state)=>runInfrastructureCallback(()=>plan.onEvent?.({
|
|
2053
|
+
type: 'project-end',
|
|
2054
|
+
project: buildProjectResult(state.prepared, state.cases, state.documents, state.lifecycle)
|
|
2055
|
+
})),
|
|
1811
2056
|
onInfrastructureError: recordInfrastructureError
|
|
1812
2057
|
});
|
|
1813
2058
|
states.forEach((state, index)=>{
|
|
@@ -1888,6 +2133,10 @@ async function runPreparedTestProject(plan) {
|
|
|
1888
2133
|
buildRunnerDump: (index)=>{
|
|
1889
2134
|
reportIndex = index;
|
|
1890
2135
|
const dump = (0, test_runner_namespaceObject.buildTestRunReportDump)(completedResult, index);
|
|
2136
|
+
completedResult = {
|
|
2137
|
+
...completedResult,
|
|
2138
|
+
metrics: dump.metrics
|
|
2139
|
+
};
|
|
1891
2140
|
const warn = (0, logger_namespaceObject.getDebug)('test-runner:report-assembler', {
|
|
1892
2141
|
console: true
|
|
1893
2142
|
});
|
|
@@ -1905,6 +2154,16 @@ async function runPreparedTestProject(plan) {
|
|
|
1905
2154
|
await publication.publish(completedResult);
|
|
1906
2155
|
});
|
|
1907
2156
|
await publish('write-result', (0, external_node_path_namespaceObject.join)((0, external_node_path_namespaceObject.dirname)(summaryPath), 'result.md'), async ()=>{
|
|
2157
|
+
if (!completedResult.metrics) {
|
|
2158
|
+
const dump = (0, test_runner_namespaceObject.buildTestRunReportDump)(completedResult, reportIndex ?? {
|
|
2159
|
+
sources: [],
|
|
2160
|
+
metrics: (0, test_runner_namespaceObject.sumTestRunReportMetrics)([])
|
|
2161
|
+
});
|
|
2162
|
+
completedResult = {
|
|
2163
|
+
...completedResult,
|
|
2164
|
+
metrics: dump.metrics
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
1908
2167
|
const markdownPath = await writeTestResultMarkdown(completedResult, reportIndex);
|
|
1909
2168
|
completedResult = {
|
|
1910
2169
|
...completedResult,
|