food-market/tests/e2e/tools/build-coverage.ts
nns 5cb2c5934c
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
test(uat): покрытие P1-P2 98% / P3 81% — цели go/no-go достигнуты
- browser-прогон доведён правками СМЫСЛА: filter:period (react-datepicker), точный sort
  по заголовку, смены (сид+драйвер открытия→POST), sort:vat (showVat), фильтр товаров (панель)
- build-coverage: цели go/no-go (P1-P2 95, P3 80, P4 70)
- матрица/цифры → docs/uat-coverage.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:53:59 +05:00

92 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Сводит экстракт элементов (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<string, { total: number; done: number }> = {}
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) => `<b style="color:${p >= target ? '#16a34a' : '#dc2626'}">${p}%</b>`
const html = `<!doctype html><meta charset="utf-8"><title>UAT coverage — ALL MARKET</title>
<style>
body{font:14px system-ui,Segoe UI,Roboto,sans-serif;margin:0;background:#f8fafc;color:#0f172a;padding:24px}
h1{font-size:20px} .muted{color:#64748b}
.cards{display:flex;gap:16px;flex-wrap:wrap;margin:16px 0}
.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:14px 18px;min-width:150px}
.card .n{font-size:26px;font-weight:700}
table{border-collapse:collapse;width:100%;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}
th,td{padding:7px 10px;border-bottom:1px solid #eef2f7;text-align:left;vertical-align:top}
th{background:#f1f5f9;font-size:12px;text-transform:uppercase;letter-spacing:.03em;color:#475569}
.ok{color:#16a34a} .no{color:#dc2626}
.chip{display:inline-block;font-size:11px;padding:1px 7px;border-radius:999px;background:#eef2f7;margin:1px}
.bar{height:8px;border-radius:4px;background:#e2e8f0;overflow:hidden;min-width:90px}
.bar>i{display:block;height:100%}
code{background:#f1f5f9;padding:0 4px;border-radius:4px}
</style>
<h1>UAT coverage — покрытие каждого элемента каждого экрана</h1>
<div class="muted">Сгенерировано ${new Date().toISOString()} · элементы извлечены из кода (web/src/pages), покрытие — браузерный прогон на стенде.</div>
<div class="cards">
<div class="card"><div class="muted">Всего элементов</div><div class="n">${total}</div></div>
<div class="card"><div class="muted">Покрыто</div><div class="n">${done}</div></div>
<div class="card"><div class="muted">Общее</div><div class="n">${summary.overall}%</div></div>
<div class="card"><div class="muted">P1–P2 (цель 95%)</div><div class="n">${badge(pct(p12.done, p12.total), 95)}</div><div class="muted">${p12.done}/${p12.total}</div></div>
<div class="card"><div class="muted">P3 (цель 80%)</div><div class="n">${badge(summary.byPriority.P3?.pct ?? 0, 80)}</div><div class="muted">${summary.byPriority.P3?.done ?? 0}/${summary.byPriority.P3?.total ?? 0}</div></div>
<div class="card"><div class="muted">P4 (цель 70%)</div><div class="n">${badge(summary.byPriority.P4?.pct ?? 0, 70)}</div><div class="muted">${summary.byPriority.P4?.done ?? 0}/${summary.byPriority.P4?.total ?? 0}</div></div>
</div>
<table>
<thead><tr><th>Пр.</th><th>Экран</th><th>%</th><th></th><th>Элементы (зелёные покрыты)</th></tr></thead>
<tbody>
${rows.map((s) => `<tr>
<td>${s.priority}</td>
<td><b>${s.title}</b><br><span class="muted">${s.route}</span></td>
<td>${badge(s.pct, tgt(s.priority))}<br><span class="muted">${s.done}/${s.elements.length}</span></td>
<td><div class="bar"><i style="width:${s.pct}%;background:${s.pct >= tgt(s.priority) ? '#16a34a' : '#f59e0b'}"></i></div></td>
<td>${s.elements.map((e: any) => `<span class="chip" style="background:${e.covered ? '#dcfce7' : '#fee2e2'};color:${e.covered ? '#166534' : '#991b1b'}" title="${(e.how || '').replace(/"/g, '')}">${e.covered ? '✓' : '✗'} ${e.key}</span>`).join(' ')}</td>
</tr>`).join('\n')}
</tbody>
</table>`
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}`)