pageqa 0.5.1 → 0.7.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/replay.js ADDED
@@ -0,0 +1,548 @@
1
+ /**
2
+ * 回放脚本(Replay Script):把一次成功的自然语言运行固化为**可零模型重放**的步骤序列。
3
+ *
4
+ * 为什么需要它:自然语言用例每次执行都要经过 LLM 解析,慢、贵、且同一用例两次跑法可能不同。
5
+ * 一旦某条用例被 LLM 跑通,它的具体操作序列(点击哪个元素、填什么值、断言什么)就是确定的,
6
+ * 把它存成脚本后就能反复快速回放,只在用例/页面变更时再回到 LLM 重新生成。
7
+ *
8
+ * 设计要点:
9
+ * - **元素定位不存 `@eN`**:引用只在产生它的那次快照内有效,存下来回放必挂。
10
+ * 改为存语义定位符(role + name + 同名序号),回放时用新快照重新解析(见 locator.ts)。
11
+ * - **默认不调用任何模型**:断言走字符串包含;`--semantic` 才启用 Jev 语义判断。
12
+ * - **占位符保持可复用**:`${timestamp}` 这类占位符按占位符形式存,回放时重新展开,
13
+ * 这样「创建 产品${timestamp}」这类用例回放仍不会撞名。
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { dirname } from "node:path";
18
+ import { closeSession, createBskOps, ensureBskReady, ensureSession, } from "./bsk/tools.js";
19
+ import { loadConfig } from "./config.js";
20
+ import { JevClient } from "./jev.js";
21
+ import { describeLocator, isRef, locatorHint, resolveLocator, } from "./locator.js";
22
+ import { debugLog, info } from "./log.js";
23
+ import { emptyUsage, parseAssertions, renderSuiteText, renderText, summarizeSuite, } from "./report.js";
24
+ import { expandVars } from "./vars.js";
25
+ export const REPLAY_FORMAT = "pageqa-replay";
26
+ export const REPLAY_VERSION = 1;
27
+ /** 源用例内容哈希(sha256 前 16 位十六进制,够用且便于人眼比对)。 */
28
+ export function scriptHash(text) {
29
+ return createHash("sha256").update(text, "utf8").digest("hex").slice(0, 16);
30
+ }
31
+ /** 把录制结果组装成脚本对象(不含文件 IO,便于单测)。 */
32
+ export function buildReplayScript(recordings, meta = {}) {
33
+ return {
34
+ format: REPLAY_FORMAT,
35
+ version: REPLAY_VERSION,
36
+ recordedAt: new Date().toISOString(),
37
+ source: {
38
+ path: meta.sourcePath ?? null,
39
+ hash: meta.sourceText ? scriptHash(meta.sourceText) : null,
40
+ },
41
+ scenarios: recordings.map((r) => ({
42
+ name: r.name,
43
+ caseSteps: r.caseSteps,
44
+ steps: r.steps,
45
+ })),
46
+ };
47
+ }
48
+ /** 写入回放脚本文件(自动创建父目录)。 */
49
+ export function writeReplayScript(path, script) {
50
+ mkdirSync(dirname(path), { recursive: true });
51
+ writeFileSync(path, JSON.stringify(script, null, 2) + "\n", "utf8");
52
+ }
53
+ /** 读取并校验回放脚本文件;结构不合法时抛出可读错误(而不是回放中途才崩)。 */
54
+ export function loadReplayScript(path) {
55
+ if (!existsSync(path)) {
56
+ throw new Error(`找不到回放脚本:${path}`);
57
+ }
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(readFileSync(path, "utf8"));
61
+ }
62
+ catch (err) {
63
+ throw new Error(`回放脚本不是合法 JSON:${path}(${err instanceof Error ? err.message : String(err)})`);
64
+ }
65
+ if (parsed?.format !== REPLAY_FORMAT) {
66
+ throw new Error(`不是 pageqa 回放脚本(format=${String(parsed?.format)}):${path}`);
67
+ }
68
+ if (typeof parsed.version !== "number" || parsed.version > REPLAY_VERSION) {
69
+ throw new Error(`回放脚本版本不支持(脚本 v${String(parsed.version)},当前支持到 v${REPLAY_VERSION}):${path}`);
70
+ }
71
+ if (!Array.isArray(parsed.scenarios) || parsed.scenarios.length === 0) {
72
+ throw new Error(`回放脚本没有可执行的场景:${path}`);
73
+ }
74
+ // 空脚本(例如录制时模型一步都没成功执行)必须明确拒绝:
75
+ // 否则回放会「0 步全部通过」,把无效脚本伪装成一次成功的回归。
76
+ const total = parsed.scenarios.reduce((n, s) => n + (Array.isArray(s.steps) ? s.steps.length : 0), 0);
77
+ if (total === 0) {
78
+ throw new Error(`回放脚本不含任何可执行步骤(录制时模型未成功执行浏览器操作):${path}\n` +
79
+ "请先跑一次自然语言用例使其通过,再用 --emit-script 重新生成。");
80
+ }
81
+ // 旧版本生成的脚本可能在定位符/用例原文里残留录制当次写死的取值;加载时就地还原,
82
+ // 免去「为一个字段重跑一次十几分钟的 LLM 用例」。
83
+ const normalized = normalizePlaceholderLiterals(parsed);
84
+ if (normalized.length > 0) {
85
+ info(`[pageqa] 已把脚本里写死的录制取值还原为占位符(回放时重新展开):${normalized.join(",")}`);
86
+ }
87
+ return parsed;
88
+ }
89
+ /** 形如 `${name}` 或 `${name:fmt}` 的运行时变量占位符。 */
90
+ const PLACEHOLDER_TOKEN = /\$\{[A-Za-z_][A-Za-z0-9_]*(?::[^}]*)?\}/;
91
+ /**
92
+ * 从「占位符写法 + 录制当次字面量」的成对字段里反推出替换表。
93
+ *
94
+ * `fill.value` / `assert_text.expectation` 与它们各自的 `recorded*` 字段就是这样一对
95
+ * (如 `自动化测试产品${timestamp}` 与 `自动化测试产品202609211103`)。
96
+ * 把占位符两侧的固定部分对齐,剩下那段就是当次展开出的取值。
97
+ */
98
+ function deriveSubstitutions(script) {
99
+ const map = new Map();
100
+ const consider = (placeholderized, literal) => {
101
+ if (!placeholderized || !literal)
102
+ return;
103
+ const m = placeholderized.match(PLACEHOLDER_TOKEN);
104
+ if (!m || m.index === undefined)
105
+ return;
106
+ const prefix = placeholderized.slice(0, m.index);
107
+ const suffix = placeholderized.slice(m.index + m[0].length);
108
+ if (!literal.startsWith(prefix) || !literal.endsWith(suffix))
109
+ return;
110
+ const token = literal.slice(prefix.length, literal.length - suffix.length);
111
+ // 太短的取值替换起来容易误伤页面里的无关数字
112
+ if (token.length < 4)
113
+ return;
114
+ map.set(token, m[0]);
115
+ };
116
+ for (const sc of script.scenarios) {
117
+ for (const st of sc.steps) {
118
+ if (st.kind === "fill")
119
+ consider(st.value, st.recordedValue);
120
+ else if (st.kind === "assert_text") {
121
+ consider(st.expectation, st.recordedExpectation);
122
+ }
123
+ }
124
+ }
125
+ return map;
126
+ }
127
+ /**
128
+ * 把脚本里**写死的录制取值**还原成占位符,返回实际发生的替换(供日志)。
129
+ *
130
+ * 为什么需要:`locator.name`(列表里点「刚创建的那条」)与 `caseSteps` 里都可能残留
131
+ * 录制当次的具体时间戳,回放时新时间戳对不上,这一步必然定位失败。
132
+ * 新录制的脚本已经不会再写死(录制时就还原了),但**旧脚本不必重跑 LLM 重录**——
133
+ * 脚本自己带着 `recorded*` 证据,加载时即可就地修好。
134
+ *
135
+ * 只改写已知会承载「动态名称」的字段:用例原文、定位符名、填入值、断言期望。
136
+ * 刻意不动 `file`(本地文件路径必须原样存在)与 `target`/`url`(可能是有意的字面量)。
137
+ */
138
+ export function normalizePlaceholderLiterals(script) {
139
+ const map = deriveSubstitutions(script);
140
+ if (map.size === 0)
141
+ return [];
142
+ let changed = 0;
143
+ const apply = (text) => {
144
+ let out = text;
145
+ for (const [literal, placeholder] of map) {
146
+ if (!out.includes(literal))
147
+ continue;
148
+ changed += 1;
149
+ out = out.split(literal).join(placeholder);
150
+ }
151
+ return out;
152
+ };
153
+ const applyLocator = (step) => {
154
+ if (!step.locator)
155
+ return;
156
+ step.locator = {
157
+ ...step.locator,
158
+ name: apply(step.locator.name),
159
+ // 祖先路径里同样可能带动态名字(如「产品库 (自动化测试产品202609211103)」)
160
+ path: (step.locator.path ?? []).map(apply),
161
+ };
162
+ };
163
+ for (const sc of script.scenarios) {
164
+ sc.caseSteps = sc.caseSteps.map(apply);
165
+ for (const st of sc.steps) {
166
+ switch (st.kind) {
167
+ case "fill":
168
+ st.value = apply(st.value);
169
+ applyLocator(st);
170
+ break;
171
+ case "assert_text":
172
+ st.expectation = apply(st.expectation);
173
+ break;
174
+ case "click":
175
+ case "hover":
176
+ case "scroll":
177
+ case "upload":
178
+ applyLocator(st);
179
+ break;
180
+ default:
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ if (changed === 0)
186
+ return [];
187
+ return [...map].map(([literal, placeholder]) => `${literal} → ${placeholder}`);
188
+ }
189
+ /**
190
+ * 检查源用例是否已变更:变更时返回提示文本(由调用方决定如何处理)。
191
+ * 只警告不失败——用例文案变了不代表页面行为变了,硬失败会天天误报。
192
+ */
193
+ export function sourceDriftNotice(script) {
194
+ const { path, hash } = script.source;
195
+ if (!path || !hash)
196
+ return null;
197
+ if (!existsSync(path))
198
+ return `源用例已不存在:${path}`;
199
+ const current = scriptHash(readFileSync(path, "utf8"));
200
+ if (current !== hash) {
201
+ return `源用例内容已变更(${path}):脚本基于录制时的版本,建议重新用 LLM 跑一次并重新生成`;
202
+ }
203
+ return null;
204
+ }
205
+ /** 把回放步骤的可读标签与被映射到的用例步骤原文拼给报告用。 */
206
+ function stepContext(step, index, caseSteps) {
207
+ const parts = [`回放第 ${index} 步(${step.kind})`];
208
+ if (step.step && step.step >= 1 && step.step <= caseSteps.length) {
209
+ parts.push(`对应用例第 ${step.step} 步:${caseSteps[step.step - 1]}`);
210
+ }
211
+ return parts.join(",");
212
+ }
213
+ /**
214
+ * 当前页面里找不到要操作的元素。
215
+ *
216
+ * 单独区分出来,是因为它与「操作失败」不是一回事:元素不存在说明**当前页面状态下这一步不需要**
217
+ * ——最典型的是录制期模型顺手点的「取 消」这类补救/清理动作(提交后弹窗没关,模型点取消补一下),
218
+ * 回放时页面更顺利、弹窗早已关闭,那个按钮根本不存在。
219
+ * 这类步骤按「跳过」处理并继续,而不是让一条 65 步的长流程在第 12 步整条报废。
220
+ */
221
+ export class LocatorMissError extends Error {
222
+ }
223
+ /**
224
+ * 解析这一步该操作哪个元素。
225
+ *
226
+ * 优先用语义定位符在当前快照里重新解析(页面微调也能命中);
227
+ * 解析不到时退回录制时的 target——它是 CSS 选择器就还能用,是 `@eN` 则已失效。
228
+ */
229
+ function resolveStepTarget(ops, target, locator, expand) {
230
+ if (locator && (locator.role || locator.name)) {
231
+ const snapshot = ops.snapshot();
232
+ const wanted = { ...locator, name: expand(locator.name) };
233
+ const ref = resolveLocator(wanted, snapshot);
234
+ if (ref)
235
+ return ref;
236
+ // CSS target 还能直接试;否则只能报错——但要顺带说清「页面上现在有什么」
237
+ if (!isRef(target))
238
+ return expand(target);
239
+ const role = wanted.role || "?";
240
+ const hint = locatorHint(wanted, snapshot);
241
+ const detail = hint.kind === "similar-name"
242
+ ? `;当前页面中名字相近的 ${role} 元素:${hint.items.join("、")}`
243
+ : hint.kind === "role-only"
244
+ ? `;当前页面有 ${hint.roleCount} 个 role=${role} 元素` +
245
+ `(${hint.items.join("、")}),但没有名字含「${wanted.name.slice(0, 2)}」的` +
246
+ `——多半是点到了另一个同名菜单/按钮`
247
+ : `;当前页面中没有 role=${role} 的可见元素,说明该菜单/弹窗此刻并未打开`;
248
+ throw new LocatorMissError(`无法在当前页面重新定位元素:${describeLocator(locator)}(录制时为 ${target})${detail}`);
249
+ }
250
+ if (!isRef(target))
251
+ return expand(target);
252
+ throw new LocatorMissError(`无法在当前页面重新定位元素:${target}(引用已失效,且录制时未拿到语义定位符)`);
253
+ }
254
+ /** 执行单个回放步骤;断言步骤额外返回解析好的断言结果。 */
255
+ async function runStep(ops, step, expand) {
256
+ switch (step.kind) {
257
+ case "navigate":
258
+ return { text: ops.navigate(expand(step.url)) };
259
+ case "click":
260
+ return {
261
+ text: ops.click(resolveStepTarget(ops, step.target, step.locator, expand)),
262
+ };
263
+ case "hover":
264
+ return {
265
+ text: ops.hover(resolveStepTarget(ops, step.target, step.locator, expand)),
266
+ };
267
+ case "scroll":
268
+ return {
269
+ text: ops.scroll(resolveStepTarget(ops, step.target, step.locator, expand)),
270
+ };
271
+ case "fill":
272
+ return {
273
+ text: ops.fill(resolveStepTarget(ops, step.target, step.locator, expand), expand(step.value)),
274
+ };
275
+ case "upload": {
276
+ const target = step.target
277
+ ? resolveStepTarget(ops, step.target, step.locator, expand)
278
+ : undefined;
279
+ return { text: ops.upload(target, expand(step.file)) };
280
+ }
281
+ case "wait":
282
+ return { text: ops.wait(step.ms) };
283
+ case "assert_text": {
284
+ const expectation = expand(step.expectation);
285
+ const out = await ops.assertText(expectation);
286
+ const parsed = parseAssertions(out)[0];
287
+ return {
288
+ text: out,
289
+ assertion: parsed ?? {
290
+ expectation,
291
+ verdict: "fail",
292
+ evidence: `无法解析断言结果:${out}`,
293
+ },
294
+ };
295
+ }
296
+ }
297
+ }
298
+ /**
299
+ * 执行一步并重试(默认最多 3 次,每次间隔 500ms)。
300
+ *
301
+ * 重试的理由有两类:bsk 侧的时序抖动(el-upload 首次触发可能返回
302
+ * `did not activate a file input`,重试即成功),以及页面动画/弹窗延迟导致元素晚出现。
303
+ * 定位每次都会重新取快照,所以对「稍后才出现」有效;代价是失败步骤多花约 1 秒,
304
+ * 比把一条长流程判死划算。
305
+ */
306
+ async function runStepWithRetry(ops, step, expand, trace, index, attempts) {
307
+ let lastError;
308
+ for (let attempt = 1; attempt <= attempts; attempt++) {
309
+ try {
310
+ return await runStep(ops, step, expand);
311
+ }
312
+ catch (err) {
313
+ lastError = err;
314
+ if (attempt >= attempts)
315
+ break;
316
+ const reason = err instanceof Error ? err.message : String(err);
317
+ trace.push(`[replay-retry] #${index} ${step.kind}(第 ${attempt}/${attempts} 次失败):${reason}`);
318
+ debugLog(`[replay] 第 ${index} 步失败,准备重试:${reason}`);
319
+ ops.wait(500);
320
+ }
321
+ }
322
+ throw lastError;
323
+ }
324
+ /**
325
+ * 场景结论:**任一断言不成立、任一失败步骤、或中途中止** → fail。
326
+ *
327
+ * 单独抽成纯函数是因为它曾经写错过一次:只统计「抛出的步骤失败」而漏掉
328
+ * 「断言返回不成立」,结果一条带 FAIL 断言的用例报了 PASS 与非零之外的退出码——
329
+ * 假通过比报错危险得多,这里用单测钉死。
330
+ * 跳过(元素未找到)不影响结论:页面真的退化时,后续步骤或断言会暴露出来。
331
+ */
332
+ export function replayScenarioStatus(outcome) {
333
+ const assertionFailed = outcome.assertions.some((a) => a.verdict === "fail");
334
+ return outcome.failed > 0 || outcome.aborted !== null || assertionFailed
335
+ ? "fail"
336
+ : "pass";
337
+ }
338
+ /**
339
+ * 按顺序执行一个场景的全部步骤(不碰 session 生命周期,便于单测)。
340
+ *
341
+ * 失败语义:
342
+ * - **元素未找到** → 记为「跳过」并继续。这是「当前页面状态下这一步不需要」,
343
+ * 而非页面退化的证据;真正的退化会由后续步骤或断言暴露出来。
344
+ * - **其它失败**(元素找到了但操作报错、断言不成立)→ 记为失败并继续,跑完给出全貌。
345
+ * - **navigate 失败** → 后续步骤已无意义,直接中止。
346
+ * 失败一律不会被吞掉:都会变成报告里的一条 FAIL 断言 + 非零退出码。
347
+ */
348
+ export async function executeReplaySteps(ops, scenario, opts) {
349
+ const outcome = {
350
+ trace: [],
351
+ assertions: [],
352
+ outputs: [],
353
+ skipped: [],
354
+ executed: 0,
355
+ failed: 0,
356
+ aborted: null,
357
+ };
358
+ const attempts = opts.attempts ?? 3;
359
+ for (const [i, step] of scenario.steps.entries()) {
360
+ const index = i + 1;
361
+ const label = `#${index} ${step.kind}`;
362
+ const startedAt = Date.now();
363
+ info(`[pageqa] ▶ ${label} …`);
364
+ try {
365
+ const out = await runStepWithRetry(ops, step, opts.expand, outcome.trace, index, attempts);
366
+ outcome.executed = index;
367
+ const cost = Date.now() - startedAt;
368
+ outcome.trace.push(`[replay-ok] ${label} ${cost}ms`);
369
+ if (out.text)
370
+ outcome.outputs.push(out.text);
371
+ if (out.assertion) {
372
+ // 录制时走 Jev 语义判断的断言,在零模型的字符串匹配下可能误报;
373
+ // 失败时点明原因并给出可执行的下一步,而不是让人盯着「不成立」猜。
374
+ if (out.assertion.verdict === "fail" &&
375
+ !opts.jevActive &&
376
+ step.kind === "assert_text" &&
377
+ step.semantic) {
378
+ out.assertion.evidence =
379
+ (out.assertion.evidence ? out.assertion.evidence + ";" : "") +
380
+ "该断言在录制时走 Jev 语义判断,字符串匹配可能误报——可加 --semantic 重试";
381
+ }
382
+ outcome.assertions.push(out.assertion);
383
+ }
384
+ info(`[pageqa] ✓ ${label} ${cost}ms` +
385
+ (out.assertion
386
+ ? `(断言${out.assertion.verdict === "pass" ? "成立" : "不成立"})`
387
+ : ""));
388
+ continue;
389
+ }
390
+ catch (err) {
391
+ const reason = err instanceof Error ? err.message : String(err);
392
+ const cost = Date.now() - startedAt;
393
+ const context = stepContext(step, index, scenario.caseSteps);
394
+ // 元素不存在 = 当前页面状态下不需要这一步,跳过并继续(但要在报告里点名)
395
+ if (err instanceof LocatorMissError && !opts.failFast) {
396
+ outcome.skipped.push(`${context}:${reason}`);
397
+ outcome.trace.push(`[replay-skip] ${label}:${reason}`);
398
+ info(`[pageqa] ⚠ ${label} ${cost}ms 元素未找到,已跳过并继续:${reason}`);
399
+ continue;
400
+ }
401
+ outcome.failed += 1;
402
+ outcome.trace.push(`[replay-error] ${label}:${reason}`);
403
+ info(`[pageqa] ✗ ${label} ${cost}ms:${reason}`);
404
+ const giveUp = step.kind === "navigate" || opts.failFast === true;
405
+ outcome.assertions.push({
406
+ expectation: `${context} 成功`,
407
+ verdict: "fail",
408
+ evidence: `${reason};已尝试 ${attempts} 次仍失败` +
409
+ (giveUp
410
+ ? `,回放在此停止` +
411
+ (index < scenario.steps.length
412
+ ? `;未执行到的步骤:回放第 ${index + 1}~${scenario.steps.length} 步`
413
+ : "")
414
+ : ",已跳过该步并继续执行剩余步骤"),
415
+ });
416
+ if (giveUp) {
417
+ outcome.aborted = label;
418
+ break;
419
+ }
420
+ }
421
+ }
422
+ return outcome;
423
+ }
424
+ /** 回放一个场景:自带 session 生命周期,逐步执行并在失败处停止。 */
425
+ async function replayScenario(name, scenario, opts) {
426
+ const now = opts.now ?? new Date();
427
+ const expand = (text) => expandVars(text, now);
428
+ let session;
429
+ let outcome = {
430
+ trace: [],
431
+ assertions: [],
432
+ outputs: [],
433
+ skipped: [],
434
+ executed: 0,
435
+ failed: 0,
436
+ aborted: null,
437
+ };
438
+ try {
439
+ await ensureBskReady();
440
+ session = ensureSession(opts.session);
441
+ info(`[pageqa] 回放 session=${session}(场景:${name})`);
442
+ const jev = opts.semantic
443
+ ? new JevClient(loadConfig().jev, opts.debug ?? false)
444
+ : undefined;
445
+ if (opts.semantic && !jev?.enabled) {
446
+ info("[pageqa] --semantic 已指定,但 Jev 未启用(缺 enabled/apiKey),断言退回字符串匹配");
447
+ }
448
+ outcome = await executeReplaySteps(createBskOps(session, jev), scenario, {
449
+ expand,
450
+ jevActive: Boolean(jev?.enabled),
451
+ failFast: opts.failFast ?? false,
452
+ });
453
+ }
454
+ finally {
455
+ if (session)
456
+ closeSession(session);
457
+ }
458
+ const { trace, assertions, skipped } = outcome;
459
+ const status = replayScenarioStatus(outcome);
460
+ const report = {
461
+ mode: "replay",
462
+ script: opts.scriptPath,
463
+ status,
464
+ assertions,
465
+ skipped,
466
+ summary: [
467
+ `回放 ${scenario.steps.length} 步,执行 ${outcome.executed} 步`,
468
+ skipped.length ? `跳过 ${skipped.length} 步(元素未找到)` : null,
469
+ outcome.failed ? `失败 ${outcome.failed} 步` : null,
470
+ ]
471
+ .filter(Boolean)
472
+ .join(","),
473
+ transcript: [
474
+ ...trace,
475
+ ...outcome.outputs.map((o) => " " + o.trim()),
476
+ ].join("\n"),
477
+ trace,
478
+ steps: scenario.caseSteps,
479
+ usage: emptyUsage(),
480
+ };
481
+ info(`[pageqa] 场景回放结束:${status === "pass" ? "PASS" : "FAIL"}` +
482
+ `(执行 ${outcome.executed}/${scenario.steps.length} 步` +
483
+ (skipped.length ? `,跳过 ${skipped.length} 步` : "") +
484
+ (outcome.failed ? `,失败 ${outcome.failed} 步` : "") +
485
+ ")");
486
+ const result = {
487
+ report,
488
+ text: renderText(report),
489
+ json: JSON.stringify(report, null, 2),
490
+ transcript: report.transcript,
491
+ usage: report.usage ?? emptyUsage(),
492
+ };
493
+ return {
494
+ result,
495
+ detail: {
496
+ name,
497
+ status: report.status,
498
+ steps: scenario.caseSteps,
499
+ trace,
500
+ assertions,
501
+ },
502
+ };
503
+ }
504
+ /**
505
+ * 回放整个脚本:单场景按单份报告输出;多场景逐个回放(各自独立 session/窗口)并汇总,
506
+ * 汇总语义与 `--suite` 一致——任一场景失败则整体失败、退出码非零。
507
+ */
508
+ export async function runReplayScript(script, opts = {}) {
509
+ const scenarios = script.scenarios;
510
+ info(`[pageqa] 回放脚本:${opts.scriptPath ?? "(内存)"},共 ${scenarios.length} 个场景,` +
511
+ `零模型执行${opts.semantic ? "(断言使用 Jev 语义判断)" : ""}`);
512
+ // 录制时启用了 Jev 的断言往往是语义措辞(如「检出成功」「标题包含 Example」),
513
+ // 默认的字符串包含匹配会误报失败。开始前就说清楚,别等跑到一半才让人猜。
514
+ if (!opts.semantic) {
515
+ const semanticAssertions = scenarios.reduce((n, sc) => n +
516
+ sc.steps.filter((s) => s.kind === "assert_text" && s.semantic).length, 0);
517
+ if (semanticAssertions > 0) {
518
+ info(`[pageqa] 注意:脚本中有 ${semanticAssertions} 条断言在录制时走 Jev 语义判断,` +
519
+ `回放默认用字符串包含匹配,可能误报失败;需要语义判断请加 --semantic`);
520
+ }
521
+ }
522
+ if (scenarios.length === 1) {
523
+ return (await replayScenario(scenarios[0].name, scenarios[0], opts)).result;
524
+ }
525
+ const outcomes = [];
526
+ for (const [i, sc] of scenarios.entries()) {
527
+ info(`[pageqa] ═══ 场景 ${i + 1}/${scenarios.length}:${sc.name} ═══`);
528
+ const outcome = await replayScenario(sc.name, sc, opts);
529
+ outcomes.push(outcome);
530
+ }
531
+ const summary = summarizeSuite(outcomes.map((o) => ({
532
+ name: o.detail.name,
533
+ report: o.result.report,
534
+ usage: o.result.usage,
535
+ })));
536
+ summary.mode = "replay";
537
+ summary.script = opts.scriptPath;
538
+ const text = renderSuiteText(summary, outcomes.map((o) => ({ report: o.result.report, usage: o.result.usage })), scenarios.map((s) => ({ name: s.name, body: "" })));
539
+ info(`[pageqa] 回放汇总:${summary.summary}`);
540
+ return {
541
+ report: summary,
542
+ text,
543
+ json: JSON.stringify(summary, null, 2),
544
+ transcript: summary.transcript,
545
+ usage: summary.usage ?? emptyUsage(),
546
+ };
547
+ }
548
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,aAAa,GAEd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,eAAe,EACf,KAAK,EACL,WAAW,EACX,cAAc,GAEf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,eAAe,EACf,eAAe,EACf,UAAU,EACV,cAAc,GAKf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAsGhC,4CAA4C;AAC5C,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,iBAAiB,CAC/B,UAA+B,EAC/B,OAAmE,EAAE;IAErE,OAAO;QACL,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,cAAc;QACvB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YAC7B,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;SAC3D;QACD,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK;SACf,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAoB;IAClE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,MAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAiB,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,IAAI,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAC7E,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,EAAE,MAAM,KAAK,aAAa,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAC3D,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,GAAG,cAAc,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CACb,iBAAiB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,cAAc,KAAK,IAAI,EAAE,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,8BAA8B;IAC9B,kCAAkC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CACnC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAC3D,CAAC,CACF,CAAC;IACF,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,kCAAkC,IAAI,IAAI;YACxC,wCAAwC,CAC3C,CAAC;IACJ,CAAC;IACD,2CAA2C;IAC3C,6BAA6B;IAC7B,MAAM,UAAU,GAAG,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,CACF,wCAAwC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC/D,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,MAAM,iBAAiB,GAAG,yCAAyC,CAAC;AAEpE;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,MAAM,QAAQ,GAAG,CAAC,eAAwB,EAAE,OAAgB,EAAQ,EAAE;QACpE,IAAI,CAAC,eAAe,IAAI,CAAC,OAAO;YAAE,OAAO;QACzC,MAAM,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACnD,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO;QACxC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO;QACrE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3E,wBAAwB;QACxB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QAC7B,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,KAAK,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC1B,IAAI,EAAE,CAAC,IAAI,KAAK,MAAM;gBAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;iBACxD,IAAI,EAAE,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;gBACnC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAoB;IAC/D,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE;QACrC,IAAI,GAAG,GAAG,IAAI,CAAC;QACf,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,GAAG,EAAE,CAAC;YACzC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,SAAS;YACrC,OAAO,IAAI,CAAC,CAAC;YACb,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,MAAM,YAAY,GAAG,CAAC,IAAiC,EAAQ,EAAE;QAC/D,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,OAAO,GAAG;YACb,GAAG,IAAI,CAAC,OAAO;YACf,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAC9B,+CAA+C;YAC/C,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;SAC3C,CAAC;IACJ,CAAC,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;gBAChB,KAAK,MAAM;oBACT,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;oBAC3B,YAAY,CAAC,EAAE,CAAC,CAAC;oBACjB,MAAM;gBACR,KAAK,aAAa;oBAChB,EAAE,CAAC,WAAW,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;oBACvC,MAAM;gBACR,KAAK,OAAO,CAAC;gBACb,KAAK,OAAO,CAAC;gBACb,KAAK,QAAQ,CAAC;gBACd,KAAK,QAAQ;oBACX,YAAY,CAAC,EAAE,CAAC,CAAC;oBACjB,MAAM;gBACR;oBACE,MAAM;YACV,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7B,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,MAAM,WAAW,EAAE,CAAC,CAAC;AACjF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,WAAW,IAAI,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,YAAY,IAAI,iCAAiC,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AA6BD,mCAAmC;AACnC,SAAS,WAAW,CAClB,IAAgB,EAChB,KAAa,EACb,SAAmB;IAEnB,MAAM,KAAK,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACjE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAE9C;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,GAAW,EACX,MAAc,EACd,OAAuB,EACvB,MAAgC;IAEhC,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,MAAM,GAAY,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACnE,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC7C,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACpB,4CAA4C;QAC5C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC;QAChC,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,MAAM,GACV,IAAI,CAAC,IAAI,KAAK,cAAc;YAC1B,CAAC,CAAC,eAAe,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YAClD,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW;gBACzB,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,WAAW,IAAI,KAAK;oBAC5C,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI;oBAC/D,oBAAoB;gBACtB,CAAC,CAAC,iBAAiB,IAAI,uBAAuB,CAAC;QACrD,MAAM,IAAI,gBAAgB,CACxB,iBAAiB,eAAe,CAAC,OAAO,CAAC,SAAS,MAAM,IAAI,MAAM,EAAE,CACrE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,IAAI,gBAAgB,CACxB,iBAAiB,MAAM,sBAAsB,CAC9C,CAAC;AACJ,CAAC;AAED,iCAAiC;AACjC,KAAK,UAAU,OAAO,CACpB,GAAW,EACX,IAAgB,EAChB,MAAgC;IAEhC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,UAAU;YACb,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAClD,KAAK,OAAO;YACV,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;aAC3E,CAAC;QACJ,KAAK,OAAO;YACV,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;aAC3E,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,MAAM,CACd,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAC1D;aACF,CAAC;QACJ,KAAK,MAAM;YACT,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,IAAI,CACZ,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EACzD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CACnB;aACF,CAAC;QACJ,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM;gBACxB,CAAC,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;gBAC3D,CAAC,CAAC,SAAS,CAAC;YACd,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACzD,CAAC;QACD,KAAK,MAAM;YACT,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACrC,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC7C,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACvC,OAAO;gBACL,IAAI,EAAE,GAAG;gBACT,SAAS,EAAE,MAAM,IAAI;oBACnB,WAAW;oBACX,OAAO,EAAE,MAAM;oBACf,QAAQ,EAAE,YAAY,GAAG,EAAE;iBAC5B;aACF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,gBAAgB,CAC7B,GAAW,EACX,IAAgB,EAChB,MAAgC,EAChC,KAAe,EACf,KAAa,EACb,QAAgB;IAEhB,IAAI,SAAkB,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,CAAC;YAChB,IAAI,OAAO,IAAI,QAAQ;gBAAE,MAAM;YAC/B,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,KAAK,CAAC,IAAI,CACR,mBAAmB,KAAK,IAAI,IAAI,CAAC,IAAI,MAAM,OAAO,IAAI,QAAQ,SAAS,MAAM,EAAE,CAChF,CAAC;YACF,QAAQ,CAAC,cAAc,KAAK,aAAa,MAAM,EAAE,CAAC,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,MAAM,SAAS,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAIpC;IACC,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;IAC7E,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,eAAe;QACtE,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,MAAM,CAAC;AACb,CAAC;AA4BD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAW,EACX,QAAwB,EACxB,IAAuB;IAEvB,MAAM,OAAO,GAAsB;QACjC,KAAK,EAAE,EAAE;QACT,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,IAAI;KACd,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IAEpC,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAChC,GAAG,EACH,IAAI,EACJ,IAAI,CAAC,MAAM,EACX,OAAO,CAAC,KAAK,EACb,KAAK,EACL,QAAQ,CACT,CAAC;YACF,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YACpC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC;YACrD,IAAI,GAAG,CAAC,IAAI;gBAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAClB,oCAAoC;gBACpC,mCAAmC;gBACnC,IACE,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,MAAM;oBAChC,CAAC,IAAI,CAAC,SAAS;oBACf,IAAI,CAAC,IAAI,KAAK,aAAa;oBAC3B,IAAI,CAAC,QAAQ,EACb,CAAC;oBACD,GAAG,CAAC,SAAS,CAAC,QAAQ;wBACpB,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;4BAC5D,+CAA+C,CAAC;gBACpD,CAAC;gBACD,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACzC,CAAC;YACD,IAAI,CACF,cAAc,KAAK,IAAI,IAAI,IAAI;gBAC7B,CAAC,GAAG,CAAC,SAAS;oBACZ,CAAC,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;oBAC1D,CAAC,CAAC,EAAE,CAAC,CACV,CAAC;YACF,SAAS;QACX,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YACpC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;YAE7D,wCAAwC;YACxC,IAAI,GAAG,YAAY,gBAAgB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACtD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC;gBAC7C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;gBACvD,IAAI,CAAC,cAAc,KAAK,IAAI,IAAI,mBAAmB,MAAM,EAAE,CAAC,CAAC;gBAC7D,SAAS;YACX,CAAC;YAED,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,cAAc,KAAK,IAAI,IAAI,MAAM,MAAM,EAAE,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;YAClE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;gBACtB,WAAW,EAAE,GAAG,OAAO,KAAK;gBAC5B,OAAO,EAAE,MAAM;gBACf,QAAQ,EACN,GAAG,MAAM,QAAQ,QAAQ,OAAO;oBAChC,CAAC,MAAM;wBACL,CAAC,CAAC,SAAS;4BACT,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM;gCAC5B,CAAC,CAAC,gBAAgB,KAAK,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI;gCACxD,CAAC,CAAC,EAAE,CAAC;wBACT,CAAC,CAAC,iBAAiB,CAAC;aACzB,CAAC,CAAC;YACH,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;gBACxB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0CAA0C;AAC1C,KAAK,UAAU,cAAc,CAC3B,IAAY,EACZ,QAAwB,EACxB,IAAmB;IAEnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvD,IAAI,OAA2B,CAAC;IAChC,IAAI,OAAO,GAAsB;QAC/B,KAAK,EAAE,EAAE;QACT,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,IAAI;KACd,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,cAAc,EAAE,CAAC;QACvB,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,uBAAuB,OAAO,OAAO,IAAI,GAAG,CAAC,CAAC;QAEnD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ;YACvB,CAAC,CAAC,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;YACtD,CAAC,CAAC,SAAS,CAAC;QACd,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;YACnC,IAAI,CACF,+DAA+D,CAChE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,MAAM,kBAAkB,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE;YACvE,MAAM;YACN,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;YAChC,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK;SACjC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,OAAO;YAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAe;QACzB,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,MAAM;QACN,UAAU;QACV,OAAO;QACP,OAAO,EAAE;YACP,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,SAAS,OAAO,CAAC,QAAQ,IAAI;YACxD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,WAAW,CAAC,CAAC,CAAC,IAAI;YACvD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI;SACjD;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC;QACZ,UAAU,EAAE;YACV,GAAG,KAAK;YACR,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;SACjD,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,KAAK;QACL,KAAK,EAAE,QAAQ,CAAC,SAAS;QACzB,KAAK,EAAE,UAAU,EAAE;KACpB,CAAC;IACF,IAAI,CACF,mBAAmB,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE;QACtD,OAAO,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI;QACpD,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,GAAG,CACN,CAAC;IAEF,MAAM,MAAM,GAAoB;QAC9B,MAAM;QACN,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,UAAU,EAAE;KACpC,CAAC;IACF,OAAO;QACL,MAAM;QACN,MAAM,EAAE;YACN,IAAI;YACJ,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,QAAQ,CAAC,SAAS;YACzB,KAAK;YACL,UAAU;SACX;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAoB,EACpB,OAAsB,EAAE;IAExB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,CACF,iBAAiB,IAAI,CAAC,UAAU,IAAI,MAAM,MAAM,SAAS,CAAC,MAAM,OAAO;QACrE,QAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CACnD,CAAC;IACF,gDAAgD;IAChD,sCAAsC;IACtC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,kBAAkB,GAAG,SAAS,CAAC,MAAM,CACzC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CACR,CAAC;YACD,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,EACvE,CAAC,CACF,CAAC;QACF,IAAI,kBAAkB,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CACF,oBAAoB,kBAAkB,qBAAqB;gBACzD,yCAAyC,CAC5C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,CAAC;IAED,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAC5B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI;QACnB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;QACvB,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK;KACtB,CAAC,CAAC,CACJ,CAAC;IACF,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IACjC,MAAM,IAAI,GAAG,eAAe,CAC1B,OAAO,EACP,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,EACzE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CACnD,CAAC;IACF,IAAI,CAAC,iBAAiB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACzC,OAAO;QACL,MAAM,EAAE,OAAO;QACf,IAAI;QACJ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,UAAU,EAAE;KACrC,CAAC;AACJ,CAAC"}
package/dist/report.d.ts CHANGED
@@ -17,6 +17,12 @@ export interface TokenUsage {
17
17
  }
18
18
  export interface TestReport {
19
19
  status: "pass" | "fail";
20
+ /** 运行模式:`llm`(自然语言解析,默认,旧报告无此字段)或 `replay`(零模型回放)。 */
21
+ mode?: "llm" | "replay";
22
+ /** 回放脚本路径(回放模式为所用脚本,LLM 模式为生成的脚本)。 */
23
+ script?: string;
24
+ /** 回放中因「元素未找到」被跳过的步骤(不算失败,但必须让人看见)。 */
25
+ skipped?: string[];
20
26
  assertions: AssertionResult[];
21
27
  summary?: string;
22
28
  transcript: string;
@@ -56,13 +62,21 @@ export declare function mergeUsage(a: TokenUsage, b: TokenUsage): TokenUsage;
56
62
  * 渲染成一行文本(报告末行)。
57
63
  * 端点未返回 usage 时如实标注,避免把「0 token」误读成真实消耗。
58
64
  */
59
- export declare function formatUsage(usage?: TokenUsage): string;
65
+ export declare function formatUsage(usage?: TokenUsage, mode?: TestReport["mode"]): string;
60
66
  /**
61
67
  * 从 agent 的结论文本中解析断言结果。agent 会以「成立/不成立」给出每个断言,
62
68
  * 并用「断言「X」:成立。证据...」句式。无法解析时降级为基于 PASS/FAIL 关键字的整体判断。
63
69
  */
64
70
  export declare function buildReport(input: string, transcript: string): TestReport;
65
- /** 统计用例(自然语言测试步骤)中的断言数量,用于校验执行完整性。 */
71
+ /** 统计用例(自然语言测试步骤)中的断言数量,用于校验执行完整性。
72
+ *
73
+ * 只统计「断言 <描述>」这类真正的断言行,排除「断言」被当作讨论对象的叙述用法
74
+ * (如「断言失败时记录日志」「请检查断言结果」「断言的写法」)。
75
+ *
76
+ * 早期实现直接统计「断言」二字出现次数,会把叙述性文字也算成断言,
77
+ * 使期望断言数虚高,进而把实际已全部通过的正常用例误判为
78
+ * 「用例中的断言全部执行(实际 N/M)」,造成假失败。
79
+ */
66
80
  export declare function countAssertions(script: string): number;
67
81
  /**
68
82
  * 把用例按非空行编号,得到确定性的步骤序列(`### 步骤 k:<原文>`)。
@@ -94,3 +108,27 @@ export declare function parseProgress(text: string): {
94
108
  total: number;
95
109
  } | null;
96
110
  export declare function parseAssertions(text: string): AssertionResult[];
111
+ /** 套件报告里每个场景最多回放多少条执行轨迹(末尾优先),避免报告过长。 */
112
+ export declare const TRACE_TAIL_LINES = 25;
113
+ /** 套件成员:一个场景的运行结果与用量(LLM 运行或回放结果都满足该结构)。 */
114
+ export interface SuiteMember {
115
+ name: string;
116
+ report: TestReport;
117
+ usage: TokenUsage;
118
+ }
119
+ /** 渲染一条断言:` - [PASS] 期望 (证据)`。 */
120
+ export declare function assertionLine(a: AssertionResult): string;
121
+ /** 渲染单场景文本报告。 */
122
+ export declare function renderText(r: TestReport): string;
123
+ /**
124
+ * 汇总多个场景:任一场景失败则整体失败。
125
+ * 断言前缀场景名、保留逐场景 `steps/trace` 明细,CI 侧可直接定位失败场景的卡点步骤。
126
+ */
127
+ export declare function summarizeSuite(members: SuiteMember[]): TestReport;
128
+ /** 渲染套件文本报告:逐场景断言 + 执行轨迹(末尾若干条)+ 最终汇总。 */
129
+ export declare function renderSuiteText(summary: TestReport, members: {
130
+ report: TestReport;
131
+ usage: TokenUsage;
132
+ }[], scenarios: {
133
+ name: string;
134
+ }[]): string;