Durable regression suite proving every public surface across Lux/Zoo/Hanzo/Pars
(explorers, wallets, id, votes, work boards, bridges, safe, clouds, exchanges)
is live, functional, and correctly branded at 390/768/1280/1920 — real
Chrome/Blink (channel:'chrome'), read-only (no txns, no wallet-connect, no auth;
the login form is asserted, never submitted).
- e2e/branded/surfaces.ts — ONE source-of-truth table (surface × posture × flags);
breakpoint-scoped flags carry a reason + flip condition
- e2e/branded/branded.spec.ts — table-driven 8-check battery (the owner's 6-point
rubric: 200 · branded head + zero cross-brand leak ·
no overflow · no app-console-errors · key control
visible · brand font); gate vs flag vs stale-flag
- e2e/branded/matrix-reporter.ts — renders the surface × breakpoint PASS/FLAG/FAIL
grid + defect ledger; writes branded-report/matrix.{md,txt,json}
- playwright.branded.config.ts — breakpoints as projects, chrome channel, matrix reporter
- e2e.yml `branded` job + README + LLM.md section
Baseline 104 cells: 67 pass · 37 flagged (each with a flip condition) · 0
regressions. Only a NEW un-flagged failure turns the run red. Screenshots →
~/Desktop/lux-shots-e2e/. Bumps @luxfi/universe 1.0.1 → 1.0.2.
Co-authored-by: Hanzo Dev <[email protected]>
176 lines
7.7 KiB
TypeScript
176 lines
7.7 KiB
TypeScript
import type { Reporter, TestCase, TestResult, FullResult } from '@playwright/test/reporter'
|
||
import { mkdirSync, writeFileSync } from 'fs'
|
||
import { join } from 'path'
|
||
import { SURFACES, BREAKPOINTS } from './surfaces'
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Matrix reporter — renders the branded regression as a surface × breakpoint
|
||
// PASS / FLAG / FAIL grid, plus a defect ledger (reason + flip per flag) and a
|
||
// stale-flag list so the table self-cleans. Prints to console and writes
|
||
// branded-report/{matrix.md,matrix.json}.
|
||
// ---------------------------------------------------------------------------
|
||
type Cell = {
|
||
cellState: 'PASS' | 'FLAG' | 'FAIL'
|
||
checks: { name: string; ok: boolean; detail: string }[]
|
||
flagged: { check: string; reason: string; flip: string; detail: string }[]
|
||
unexpected: { name: string; detail: string }[]
|
||
staleFlags: string[]
|
||
accentNote: string
|
||
bodyForeign: string[]
|
||
posture: string
|
||
brand: string
|
||
url: string
|
||
}
|
||
|
||
const BP_NAMES = BREAKPOINTS.map((b) => b.name)
|
||
const GLYPH: Record<string, string> = { PASS: ' ✓ ', FLAG: ' ⚑ ', FAIL: ' ✗ ', '': ' · ' }
|
||
|
||
export default class MatrixReporter implements Reporter {
|
||
private cells = new Map<string, Cell>() // key = `${id}::${bp}`
|
||
private outDir = join(process.cwd(), 'branded-report')
|
||
|
||
onBegin() {
|
||
mkdirSync(this.outDir, { recursive: true })
|
||
}
|
||
|
||
onTestEnd(test: TestCase, result: TestResult) {
|
||
const att = result.attachments.find((a) => a.name === 'branded-result' && a.body)
|
||
if (!att || !att.body) {
|
||
// Crashed/timed out before attaching — synthesize a FAIL cell keyed by title+project.
|
||
const bp = test.parent?.project()?.name || test.titlePath()[1] || '?'
|
||
this.cells.set(`${test.title}::${bp}`, {
|
||
cellState: 'FAIL', checks: [], flagged: [],
|
||
unexpected: [{ name: 'run', detail: `${result.status} (no result attached)` }],
|
||
staleFlags: [], accentNote: '', bodyForeign: [], posture: '', brand: '', url: '',
|
||
})
|
||
return
|
||
}
|
||
const d = JSON.parse(att.body.toString())
|
||
this.cells.set(`${d.id}::${d.bp}`, d)
|
||
}
|
||
|
||
onEnd(result: FullResult) {
|
||
const lines: string[] = []
|
||
const w = 15
|
||
const pad = (s: string, n: number) => (s + ' '.repeat(n)).slice(0, n)
|
||
|
||
// ── Header ──
|
||
lines.push('')
|
||
lines.push('═'.repeat(72))
|
||
lines.push(' BRANDED WEB3-SURFACE MATRIX — live · functional · branded @ 4 breakpoints')
|
||
lines.push(' (real Chrome/Blink · read-only · ✓ pass ⚑ flagged/expected ✗ regression)')
|
||
lines.push('═'.repeat(72))
|
||
lines.push('')
|
||
lines.push(pad(' surface', w + 9) + BP_NAMES.map((b) => pad(' ' + b, 6)).join('') + ' brand')
|
||
lines.push(' ' + '─'.repeat(w + 7 + BP_NAMES.length * 6 + 8))
|
||
|
||
let pass = 0, flag = 0, fail = 0
|
||
let lastCat = ''
|
||
const ledger: string[] = []
|
||
const stales: string[] = []
|
||
|
||
for (const s of SURFACES) {
|
||
if (s.category !== lastCat) { lines.push(` ${s.category.toUpperCase()}`); lastCat = s.category }
|
||
const row = [pad(' ' + s.id, w + 9)]
|
||
for (const bp of BP_NAMES) {
|
||
const c = this.cells.get(`${s.id}::${bp}`)
|
||
const state = c?.cellState || ''
|
||
row.push(pad(GLYPH[state] || GLYPH[''], 6))
|
||
if (state === 'PASS') pass++; else if (state === 'FLAG') flag++; else if (state === 'FAIL') fail++
|
||
}
|
||
// Use the 1280 cell (or any) for the per-surface annotation summary.
|
||
const rep = this.cells.get(`${s.id}::1280`) || this.cells.get(`${s.id}::${BP_NAMES[0]}`)
|
||
row.push(' ' + s.brand)
|
||
lines.push(row.join(''))
|
||
|
||
if (rep) {
|
||
for (const f of dedupe(rep.flagged, (x) => x.check)) {
|
||
ledger.push(` ⚑ ${pad(s.id, w)} ${f.check.padEnd(8)} ${f.reason}`)
|
||
if (f.flip) ledger.push(` ${' '.repeat(w + 12)}↳ FLIP: ${f.flip}`)
|
||
}
|
||
for (const u of dedupe(rep.unexpected, (x) => x.name)) {
|
||
ledger.push(` ✗ ${pad(s.id, w)} ${u.name.padEnd(8)} REGRESSION — ${u.detail}`)
|
||
}
|
||
if (rep.staleFlags?.length) stales.push(` ${pad(s.id, w)} ${rep.staleFlags.join(', ')} — now passing; delete the flag in surfaces.ts`)
|
||
}
|
||
}
|
||
|
||
// ── Summary ──
|
||
const total = pass + flag + fail
|
||
lines.push('')
|
||
lines.push(' ' + '─'.repeat(w + 7 + BP_NAMES.length * 6 + 8))
|
||
lines.push(` cells: ${total} ✓ ${pass} pass ⚑ ${flag} flagged ✗ ${fail} regressions`)
|
||
lines.push('')
|
||
|
||
if (ledger.length) {
|
||
lines.push(' DEFECT LEDGER (⚑ documented + flip condition · ✗ new regression)')
|
||
lines.push(' ' + '─'.repeat(68))
|
||
lines.push(...ledger)
|
||
lines.push('')
|
||
}
|
||
if (stales.length) {
|
||
lines.push(' STALE FLAGS (a documented defect is now passing — clean up surfaces.ts)')
|
||
lines.push(' ' + '─'.repeat(68))
|
||
lines.push(...stales)
|
||
lines.push('')
|
||
}
|
||
|
||
// ── Accent notes (observational) ──
|
||
lines.push(' ACCENT / FONT NOTES (observational)')
|
||
lines.push(' ' + '─'.repeat(68))
|
||
for (const s of SURFACES) {
|
||
const rep = this.cells.get(`${s.id}::1280`) || this.cells.get(`${s.id}::${BP_NAMES[0]}`)
|
||
if (rep?.accentNote) {
|
||
const fontChk = rep.checks.find((c) => c.name === 'font')
|
||
lines.push(` ${pad(s.id, w)} ${rep.accentNote}${fontChk ? ` · font: ${fontChk.detail}` : ''}${rep.bodyForeign?.length ? ` · body-ref: ${rep.bodyForeign.join(',')}` : ''}`)
|
||
}
|
||
}
|
||
lines.push('')
|
||
lines.push(` overall playwright status: ${result.status}`)
|
||
lines.push('═'.repeat(72))
|
||
lines.push('')
|
||
|
||
const text = lines.join('\n')
|
||
console.log(text)
|
||
|
||
// ── Persist artifacts ──
|
||
const md = this.toMarkdown(pass, flag, fail, ledger, stales)
|
||
writeFileSync(join(this.outDir, 'matrix.txt'), text)
|
||
writeFileSync(join(this.outDir, 'matrix.md'), md)
|
||
writeFileSync(join(this.outDir, 'matrix.json'), JSON.stringify({
|
||
generatedAt: new Date().toISOString(),
|
||
summary: { total, pass, flag, fail, status: result.status },
|
||
cells: Object.fromEntries(this.cells),
|
||
}, null, 2))
|
||
console.log(` matrix written → ${join(this.outDir, 'matrix.md')}`)
|
||
}
|
||
|
||
private toMarkdown(pass: number, flag: number, fail: number, ledger: string[], stales: string[]): string {
|
||
const out: string[] = []
|
||
out.push('# Branded web3-surface matrix', '')
|
||
out.push('Live · functional · branded verification at 4 breakpoints (390 / 768 / 1280 / 1920), real Chrome/Blink, read-only.', '')
|
||
out.push(`**${pass} pass · ${flag} flagged · ${fail} regressions**`, '')
|
||
out.push('Legend: ✓ pass · ⚑ documented flag (expected, carries a flip condition) · ✗ regression (un-flagged failure) · · not run', '')
|
||
out.push('| surface | brand | ' + BP_NAMES.join(' | ') + ' |')
|
||
out.push('|---|---|' + BP_NAMES.map(() => '---').join('|') + '|')
|
||
let lastCat = ''
|
||
for (const s of SURFACES) {
|
||
if (s.category !== lastCat) { out.push(`| **${s.category}** | | ${BP_NAMES.map(() => '').join(' | ')} |`); lastCat = s.category }
|
||
const cells = BP_NAMES.map((bp) => {
|
||
const st = this.cells.get(`${s.id}::${bp}`)?.cellState || ''
|
||
return st === 'PASS' ? '✓' : st === 'FLAG' ? '⚑' : st === 'FAIL' ? '✗' : '·'
|
||
})
|
||
out.push(`| ${s.id} | ${s.brand} | ${cells.join(' | ')} |`)
|
||
}
|
||
out.push('')
|
||
if (ledger.length) { out.push('## Defect ledger', '', '```', ...ledger, '```', '') }
|
||
if (stales.length) { out.push('## Stale flags (now passing — clean up `surfaces.ts`)', '', '```', ...stales, '```', '') }
|
||
return out.join('\n')
|
||
}
|
||
}
|
||
|
||
function dedupe<T>(arr: T[], key: (x: T) => string): T[] {
|
||
const seen = new Set<string>()
|
||
return arr.filter((x) => { const k = key(x); if (seen.has(k)) return false; seen.add(k); return true })
|
||
}
|