export type StepStatus = 'PASSED' | 'FAILED' | 'ERROR' | 'SKIPPED'; export interface StepRecord { index: number; label?: string; type: string; status: StepStatus; /** 简短结果描述(错误消息/mismatch 摘要),≤200 字符 */ detail?: string; /** SKIPPED 原因(budget exhausted / aborted after failure / setup failed) */ skip_reason?: string; mismatch?: Record; evidence?: { screenshot_path?: string; }; elapsed_ms: number; } export interface QaReport { version: 1; run_id: string; suite: { name: string; project_path: string; started_at: string; spec_source: 'inline' | 'file' | 'cli'; }; options: Record; setup_error?: string; /** teardown(stop_project 等)尽力收尾的失败记录,不影响 status 判定 */ teardown_warnings?: string[]; /** record_on_failure 且结果非 PASSED 时,录制文件路径(qa-reports/-recording.json,格式与 recording_play 的 events_json 兼容) */ recording_path?: string; summary: { total: number; passed: number; failed: number; errors: number; skipped: number; status: 'PASSED' | 'FAILED' | 'CANCELLED'; duration_ms: number; }; steps: StepRecord[]; } export declare function qaReportsDir(): string; /** 文件名安全化:非 [A-Za-z0-9_-] 一律 _ */ export declare function sanitizeSuiteName(name: string): string; export declare function timestampStem(d?: Date): string; /** 落盘 .json + .md,返回两个路径 */ export declare function writeReport(report: QaReport): { json_path: string; md_path: string; }; /** 落盘前生成 run_id(时间戳-套件名-4位随机;PR-2 加随机后缀防同秒同名覆盖,PR-1b M-7)。 * rand4 兜底:Math.random().toString(16) 可能短于 4 位,padEnd 补 '0'。 */ export declare function makeRunId(suiteName: string): string; export declare function renderMarkdown(report: QaReport): string; /** 列出报告文件名(stem.json,按时间倒序 = 最新在前) */ export declare function listReports(): string[]; /** 读报告:'latest'/'prev'/空 → 最新/次新;裸 run_id 或文件名在 qa-reports 内查。 * 审查 Important-1 修复:带路径分隔符的 ref 必须解析进 qaReportsDir() 内—— * 拒绝任意路径读取(此前 existsSync(ref) 直读绝对路径绕过白名单)。 */ export declare function readReport(pathRef: string): QaReport; export declare function stepCaseId(st: StepRecord): string; /** * 找同套件的上一次运行报告(QA 收尾批② nightly 基线):按 run_id 文件名时间序, * 从 excludeRunId 之后往前找第一个同 sanitizeSuiteName 后缀的报告;找不到返回 null * (首次运行无基线,nightly 对该套件跳过 diff 只报告本次结果)。 * 审查 Important-2:sanitize 是有损压缩('a b' 与 'a_b' 同后缀),文件名只做粗筛, * 命中后必读 JSON 校验原始 suite.name——碰撞套件的历史报告不作为基线(防止 diff 静默失真)。 * PR-2 粗筛双兼容:run_id 加随机后缀后为三段式(...-{sanitize}-{rand4}),中段匹配; * 两段式历史落盘报告(...-{sanitize}.json)仍按 endsWith 命中——旧基线不丢。 */ export declare function findPreviousReport(excludeRunId: string, suiteName: string): QaReport | null; export interface QaDiff { base_run_id: string; head_run_id: string; regressions: { case: string; base: StepStatus; head: StepStatus; head_detail?: string; }[]; fixed: { case: string; base: StepStatus; head: StepStatus; }[]; added: { case: string; head: StepStatus; }[]; removed: { case: string; base: StepStatus; }[]; unchanged_passed: number; verdict: 'REGRESSED' | 'IMPROVED' | 'NO_STATUS_CHANGE'; } /** 按用例 id(label 优先,回退 index:type)对比两份报告。PASSED 之外的都算 not-passed。 */ export declare function diffReports(base: QaReport, head: QaReport): QaDiff;