/** * Сводит экстракт элементов (docs/uat-coverage.expected.json) и результат браузерного * прогона (reports/uat-covered.json) → docs/uat-coverage.json + docs/uat-coverage.html * с процентами покрытия по экрану, по приоритету и общим. Цель: P1–P3 ≥95%, P4 ≥70%. */ import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' const HERE = dirname(fileURLToPath(import.meta.url)) const EXP = resolve(HERE, '../../../docs/uat-coverage.expected.json') const COV = resolve(HERE, '../reports/uat-covered.json') const OUTJSON = resolve(HERE, '../../../docs/uat-coverage.json') const OUTHTML = resolve(HERE, '../../../docs/uat-coverage.html') const exp = JSON.parse(readFileSync(EXP, 'utf8')) const cov = existsSync(COV) ? JSON.parse(readFileSync(COV, 'utf8')).covered : {} const rows: any[] = [] const byPrio: Record = {} let total = 0, done = 0 for (const s of exp.screens) { const c = cov[s.route] ?? {} const els = s.elements.map((e: any) => { const hit = c[e.key]?.covered === true if (hit) done++ total++ ;(byPrio[s.priority] ??= { total: 0, done: 0 }).total++ if (hit) byPrio[s.priority].done++ return { ...e, covered: hit, how: c[e.key]?.how ?? '' } }) const scDone = els.filter((e: any) => e.covered).length rows.push({ ...s, elements: els, pct: s.elements.length ? Math.round((scDone / s.elements.length) * 100) : 100, done: scDone }) } const pct = (d: number, t: number) => (t ? Math.round((d / t) * 100) : 100) const summary = { overall: pct(done, total), total, done, byPriority: Object.fromEntries(Object.entries(byPrio).map(([k, v]) => [k, { pct: pct(v.done, v.total), ...v }])), targets: { P1: 95, P2: 95, P3: 80, P4: 70 }, } writeFileSync(OUTJSON, JSON.stringify({ generatedAt: new Date().toISOString(), summary, screens: rows }, null, 2)) const tgt = (p: string) => (p === 'P4' ? 70 : p === 'P3' ? 80 : 95) // go/no-go: P1-P2>=95, P3>=80, P4>=70 const p12 = ['P1','P2'].reduce((a,k)=>({total:a.total+(byPrio[k]?.total??0),done:a.done+(byPrio[k]?.done??0)}),{total:0,done:0}) const p13 = ['P1', 'P2', 'P3'].reduce((a, k) => ({ total: a.total + (byPrio[k]?.total ?? 0), done: a.done + (byPrio[k]?.done ?? 0) }), { total: 0, done: 0 }) const badge = (p: number, target: number) => `${p}%` const html = `UAT coverage — ALL MARKET

UAT coverage — покрытие каждого элемента каждого экрана

Сгенерировано ${new Date().toISOString()} · элементы извлечены из кода (web/src/pages), покрытие — браузерный прогон на стенде.
Всего элементов
${total}
Покрыто
${done}
Общее
${summary.overall}%
P1–P2 (цель 95%)
${badge(pct(p12.done, p12.total), 95)}
${p12.done}/${p12.total}
P3 (цель 80%)
${badge(summary.byPriority.P3?.pct ?? 0, 80)}
${summary.byPriority.P3?.done ?? 0}/${summary.byPriority.P3?.total ?? 0}
P4 (цель 70%)
${badge(summary.byPriority.P4?.pct ?? 0, 70)}
${summary.byPriority.P4?.done ?? 0}/${summary.byPriority.P4?.total ?? 0}
${rows.map((s) => ``).join('\n')}
Пр.Экран%Элементы (зелёные покрыты)
${s.priority} ${s.title}
${s.route}
${badge(s.pct, tgt(s.priority))}
${s.done}/${s.elements.length}
${s.elements.map((e: any) => `${e.covered ? '✓' : '✗'} ${e.key}`).join(' ')}
` writeFileSync(OUTHTML, html) console.log(`[coverage] overall ${summary.overall}% (${done}/${total})`) for (const [k, v] of Object.entries(summary.byPriority)) console.log(` ${k}: ${(v as any).pct}% (${(v as any).done}/${(v as any).total})`) console.log(` P1-P2: ${pct(p12.done, p12.total)}% (${p12.done}/${p12.total}) · цель 95`) console.log(` P3: ${summary.byPriority.P3?.pct}% · цель 80 · P1-P3: ${pct(p13.done, p13.total)}%`) console.log(`→ ${OUTHTML}`)