chore(qa): 验收台账与证据入库 + 构建/部署配置同步

- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。
- 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。
- 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径;
  next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐
  standalone 产物装配与部署形态。
This commit is contained in:
2026-09-28 10:48:09 +08:00
parent 6bb7c557ee
commit a0328a623f
128 changed files with 22455 additions and 504 deletions
@@ -0,0 +1,68 @@
// 从 chain6 的 allure-results 里取出「本轮真正跑过的用例」按状态分组,
// 目的:给 ② 的 28 skipped 一份**具名**证据(哪些用例、在哪个 project、以什么理由跳过),
// 而不是只引用汇总行里的计数。汇总计数只能证明「数量」,证明不了「没有把失败洗成跳过」。
import { readdirSync, statSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
const DIR = resolve(process.cwd() ?? '.', 'e2e/allure-results');
// chain6 功能阶段:03:52:08Z → 04:20:43Z;视觉阶段紧接其后。留出余量按 UTC 当日 03:50–04:25 过滤。
const FROM = Date.parse('2026-09-22T03:50:00Z');
const TO = Date.parse('2026-09-22T04:25:00Z');
const files = readdirSync(DIR).filter((f) => f.endsWith('-result.json'));
const rows = [];
for (const f of files) {
const p = resolve(DIR, f);
const m = statSync(p).mtimeMs;
if (m < FROM || m > TO) continue;
let j;
try {
j = JSON.parse(readFileSync(p, 'utf8'));
} catch {
continue;
}
const labels = {};
for (const l of j.labels || []) labels[l.name] = l.value;
rows.push({
file: f,
mtime: new Date(m).toISOString(),
status: j.status,
reason: j.statusDetails?.message || j.statusDetails?.trace || '',
project: labels.device || labels.suiteParent || '',
suite: labels.suite || '',
fullName: j.fullName || '',
name: j.name || '',
});
}
const byStatus = {};
for (const r of rows) byStatus[r.status] = (byStatus[r.status] || 0) + 1;
const skipped = rows.filter((r) => r.status === 'skipped').sort((a, b) => (a.fullName + a.project).localeCompare(b.fullName + b.project));
const out = {
generatedAt: new Date().toISOString(),
source: 'e2e/allure-results(chain6 最终代码树 `npm run test` 功能+视觉两阶段的 allure 落盘)',
window: { from: new Date(FROM).toISOString(), to: new Date(TO).toISOString() },
totalResults: rows.length,
byStatus,
skippedCount: skipped.length,
// 按 (suite, reason) 聚合,便于核对「每处 test.skip 条件命中了多少条」
skipTally: Object.entries(
skipped.reduce((acc, r) => {
const k = `${r.suite} :: ${r.reason.slice(0, 80)}`;
acc[k] = (acc[k] || 0) + 1;
return acc;
}, {}),
).map(([k, n]) => ({ group: k, count: n })),
skipped,
};
writeFileSync(
resolve(process.cwd() ?? '.', 'docs/acceptance/2026-09-21-gates/skipped-tests-final-tree.json'),
JSON.stringify(out, null, 2),
);
console.log(`window results=${rows.length} byStatus=${JSON.stringify(byStatus)}`);
console.log(`skipped=${skipped.length}`);
for (const t of out.skipTally) console.log(` x${t.count} ${t.group}`);
const nonPass = rows.filter((r) => r.status !== 'passed' && r.status !== 'skipped');
console.log(`otherNonPassed=${nonPass.length}`);
for (const r of nonPass.slice(0, 10)) console.log(` ${r.status} ${r.project} ${r.fullName} :: ${r.reason.slice(0, 120)}`);