Files
zeekayandHanzo Dev 6efce1c2d2 test(e2e): branded web3-surface matrix — live · functional · branded @ 4 breakpoints
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]>
2026-07-26 15:46:04 -07:00

239 lines
14 KiB
TypeScript
Raw Permalink 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.
import { test, expect, Page } from '@playwright/test'
import { mkdirSync } from 'fs'
import { homedir } from 'os'
import { join } from 'path'
import { SURFACES, ORG_BRANDS, BRAND_TOKENS, type Surface, type Check, type Brand } from './surfaces'
// ---------------------------------------------------------------------------
// Branded web3-surface regression — surfaces × 4 breakpoints, real Blink (Chrome).
// ---------------------------------------------------------------------------
// Read-only against LIVE production surfaces: NO transactions, NO wallet connect,
// NO auth. We assert the login/connect FORM renders — never authenticate.
// Breakpoints are Playwright PROJECTS (390/768/1280/1920) → the matrix is
// (surface row × project column). Each test runs the full 6-point battery, then
// consults surfaces.ts to split failures into EXPECTED (documented flags → the
// cell is FLAG, run stays green) vs UNEXPECTED (a real regression → the test
// FAILS red). Results are attached as JSON for the matrix reporter.
const SHOTS = join(homedir(), 'Desktop', 'lux-shots-e2e')
mkdirSync(SHOTS, { recursive: true })
// Third-party telemetry / benign browser-chrome noise that is NOT an app-origin
// error. Allow-listed with rationale — a matching console.error never gates.
const CONSOLE_ALLOWLIST: { re: RegExp; why: string }[] = [
{ re: /Failed to load resource: the server responded with a status of (403|404|429)/i, why: 'benign resource 4xx (missing icon/optional endpoint) — does not break render' },
{ re: /Content Security Policy directive .* is ignored when delivered via a <meta>/i, why: 'CSP-in-<meta> browser advisory, not an app error' },
{ re: /cloudflareinsights|beacon\.min\.js/i, why: 'Cloudflare web-analytics beacon (3rd-party telemetry)' },
{ re: /walletconnect|pulse\.walletconnect|explorer-api\.walletconnect|web3modal/i, why: 'WalletConnect telemetry/registry (3rd-party)' },
{ re: /Trustpilot/i, why: '3rd-party Trustpilot widget' },
{ re: /google-analytics|googletagmanager|gtag|hotjar|segment\.io|sentry/i, why: '3rd-party analytics/monitoring' },
{ re: /downloadable font|Failed to decode downloaded font|OTS parsing error/i, why: 'font CDN advisory (captured separately by the font check)' },
]
interface Probe {
status: number
reachable: boolean
gotoErr: string
title: string
headText: string // title + all <meta> content joined — the branded <head>
headAssets: string // favicon / apple-touch / logo asset URLs joined
faviconHref: string
bodyFont: string
scrollWidth: number
innerWidth: number
rootKids: number
bodyLen: number
bodySample: string
keyMatch: string // accessible-name of the first VISIBLE control matching surface.key
bodyForeign: string[] // foreign org brands appearing in BODY (observational note only)
accents: string[] // prominent rendered accent colors (observational)
}
// One in-page read of everything the checks need. Deterministic, single round-trip.
async function readSurface(page: Page, s: Surface): Promise<Probe> {
return page.evaluate(({ keySource, brand, brands }) => {
const keyRe = new RegExp(keySource, 'i')
const vis = (el: Element) => {
const r = el.getBoundingClientRect()
const st = getComputedStyle(el)
return r.width > 0 && r.height > 0 && st.visibility !== 'hidden' && st.display !== 'none' && st.opacity !== '0'
}
const metas = Array.from(document.querySelectorAll('meta[content]'))
.map((m) => m.getAttribute('content') || '').join(' | ')
const iconEls = Array.from(document.querySelectorAll('link[rel~="icon"], link[rel="shortcut icon"], link[rel*="apple-touch"]'))
const logoEls = Array.from(document.querySelectorAll('header img, nav img, a[href="/"] img, [class*="logo" i] img'))
const headAssets = [...iconEls.map((l) => l.getAttribute('href') || ''), ...logoEls.map((i) => i.getAttribute('src') || '')].join(' | ')
const favicon = (document.querySelector('link[rel~="icon"]') || document.querySelector('link[rel="shortcut icon"]'))?.getAttribute('href') || ''
// First VISIBLE interactive control whose accessible-name matches the key regex.
let keyMatch = ''
for (const el of Array.from(document.querySelectorAll('button, a[href], input, [role="button"], [role="link"]'))) {
if (!vis(el)) continue
const name = ((el as HTMLElement).innerText || el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('value') || '').trim()
if (name && keyRe.test(name)) { keyMatch = name.slice(0, 40); break }
}
const bt = (document.body?.innerText || '').replace(/\s+/g, ' ').trim()
const bodyForeign = brands.filter((b) => b !== brand && new RegExp('\\b' + b + '\\b').test(bt))
// Prominent accent colors from styled controls (observational).
const neutral = (c: string) => !c || /rgba?\(0,\s*0,\s*0(,|\))|rgb\(255,\s*255,\s*255\)|rgba?\(0,\s*0,\s*0,\s*0\)|rgb\(0,\s*0,\s*0\)/.test(c.replace(/\s/g, ''))
const accents = new Set<string>()
for (const el of Array.from(document.querySelectorAll('button, a, [class*="btn" i], [class*="primary" i]')).filter(vis).slice(0, 40)) {
const st = getComputedStyle(el)
if (!neutral(st.backgroundColor)) accents.add(st.backgroundColor)
if (!neutral(st.color)) accents.add(st.color)
}
return {
title: document.title,
headText: (document.title + ' | ' + metas),
headAssets,
faviconHref: favicon,
bodyFont: getComputedStyle(document.body).fontFamily,
scrollWidth: document.documentElement.scrollWidth,
innerWidth: window.innerWidth,
rootKids: (document.getElementById('root') || document.getElementById('app') || document.getElementById('__next') || document.body)?.querySelectorAll('*').length ?? -1,
bodyLen: bt.length,
bodySample: bt.slice(0, 160),
keyMatch,
bodyForeign,
accents: [...accents].slice(0, 8),
}
}, { keySource: s.key.source, brand: s.brand, brands: ORG_BRANDS as string[] }) as Promise<Omit<Probe, 'status' | 'reachable' | 'gotoErr'>> as any
}
// Classify a rendered accent set against the brand token hue (observational note).
function accentNote(brand: Brand, accents: string[]): string {
const want = BRAND_TOKENS[brand].accent
const hue = (c: string): string => {
const m = c.match(/(\d+),\s*(\d+),\s*(\d+)/)
if (!m) return ''
const [r, g, b] = [+m[1], +m[2], +m[3]]
if (b > 150 && b > g + 20 && r > g - 10 && r < b) return 'violet'
if (g > 150 && g > r + 30 && g > b) return 'green'
if (r > 150 && r > g + 30 && g > b) return 'orange'
if (r > 180 && g > 150 && b < 120) return 'gold'
return ''
}
const hues = new Set(accents.map(hue).filter(Boolean))
if (hues.has(want)) return `accent ${want} ✓`
if (hues.size) return `accent ${[...hues].join('/')} (brand token = ${want})`
return `accent undetected (brand token = ${want}; dark-theme neutral controls)`
}
interface CheckResult { name: Check; ok: boolean; detail: string }
for (const s of SURFACES) {
test.describe(`${s.category} · ${s.id} (${s.brand})`, () => {
test(s.id, async ({ page }, testInfo) => {
const bp = testInfo.project.name
const consoleErrs: string[] = []
const pageErrs: string[] = []
page.on('console', (m) => { if (m.type() === 'error') consoleErrs.push(m.text()) })
page.on('pageerror', (e) => pageErrs.push(String(e)))
// ── Load (read-only; goto may throw for a documented-down surface) ──────
let status = 0, reachable = false, gotoErr = ''
try {
const res = await page.goto(s.url, { waitUntil: 'domcontentloaded', timeout: 45_000 })
status = res?.status() ?? 0
reachable = true
await page.waitForTimeout(4500) // let the SPA hydrate + fire late console errors
} catch (e) {
gotoErr = String(e).split('\n')[0]
}
const empty = { title: '', headText: '', headAssets: '', faviconHref: '', bodyFont: '', scrollWidth: 0, innerWidth: 0, rootKids: -1, bodyLen: 0, bodySample: '', keyMatch: '', bodyForeign: [] as string[], accents: [] as string[] }
// Bound the DOM read so a heavy/broken page (e.g. exchange-fork thrash) can
// never hang the whole test — we classify from partial data + flags instead.
const read = reachable
? await Promise.race<Omit<Probe, 'status' | 'reachable' | 'gotoErr'>>([
readSurface(page, s),
new Promise((r) => setTimeout(() => r(empty), 12_000)),
]).catch(() => empty)
: empty
const p: Probe = { status, reachable, gotoErr, ...read }
// Screenshot every cell (best-effort, bounded).
if (reachable) await page.screenshot({ path: join(SHOTS, `${s.id}-${bp}.png`), fullPage: false, timeout: 12_000 }).catch(() => {})
// ── The six-point battery → eight concrete checks ───────────────────────
const foreign = ORG_BRANDS.filter((b) => b !== s.brand)
// Head/asset leak = the white-label invariant. Body brand references (bridge
// "Lux C-Chain" source chain, wallet "Avalanche" network) are legitimate and
// captured only as a note.
const headLeaks = foreign.filter((b) =>
new RegExp('\\b' + b + '\\b').test(p.headText) ||
new RegExp('(^|[/_-])' + b + '([/_. -]|$)', 'i').test(p.headAssets))
const brokenSerif = /(^|,)\s*Times(\b|,|$)|(^|,)\s*serif\s*$|Arial,\s*serif/i.test(p.bodyFont)
const badConsole = consoleErrs.filter((t) => !CONSOLE_ALLOWLIST.some((a) => a.re.test(t)))
const appErrs = [...pageErrs, ...badConsole]
const checks: CheckResult[] = [
{ name: 'http', ok: status === 200, detail: reachable ? `HTTP ${status}` : `unreachable: ${gotoErr}` },
{ name: 'title', ok: new RegExp('\\b' + s.brand + '\\b', 'i').test(p.title), detail: JSON.stringify(p.title) },
{ name: 'favicon', ok: !!p.faviconHref && p.faviconHref !== 'data:,', detail: p.faviconHref ? p.faviconHref.slice(0, 48) : '(none)' },
{ name: 'leak', ok: headLeaks.length === 0, detail: headLeaks.length ? `HEAD leaks ${headLeaks.join(',')}` : 'clean head' },
{ name: 'overflow', ok: reachable && p.scrollWidth - p.innerWidth <= 2, detail: `scrollW ${p.scrollWidth} vs innerW ${p.innerWidth}` },
{ name: 'console', ok: appErrs.length === 0, detail: appErrs.length ? `${appErrs.length} app err(s): ${appErrs.slice(0, 2).map((e) => e.slice(0, 90)).join(' ⁙ ')}` : '0 app errors' },
{ name: 'key', ok: !!p.keyMatch, detail: p.keyMatch ? `visible: "${p.keyMatch}"` : `no visible control matching ${s.key}` },
{ name: 'font', ok: reachable && !brokenSerif, detail: p.bodyFont.slice(0, 44) },
]
// ── Gate vs flag ────────────────────────────────────────────────────────
// posture 'down'/'foreign-domain' → nothing gates (whole surface is a FLAG).
const nonGating = s.posture !== 'live'
const covers = (fl: { bps?: string[] } | undefined) => !!fl && (!fl.bps || fl.bps.includes(bp))
const failed = checks.filter((c) => !c.ok)
const flagged: { check: Check; reason: string; flip: string; detail: string }[] = []
const unexpected: CheckResult[] = []
for (const f of failed) {
const known = s.flags[f.name]
if (nonGating) {
const g = s.flags[f.name] || s.flags.http
flagged.push({ check: f.name, reason: g?.reason || `${s.posture} surface`, flip: g?.flip || '', detail: f.detail })
} else if (covers(known)) {
flagged.push({ check: f.name, reason: known!.reason, flip: known!.flip, detail: f.detail })
} else {
unexpected.push(f) // no flag, or a flag that does not cover THIS breakpoint → gates
}
}
// A documented flag whose check is PASSING at a breakpoint it claims → stale.
const staleFlags = (Object.keys(s.flags) as Check[]).filter(
(k) => s.posture === 'live' && covers(s.flags[k]) && checks.find((c) => c.name === k)?.ok,
)
const accNote = accentNote(s.brand, p.accents)
const cellState = unexpected.length ? 'FAIL' : (flagged.length || nonGating) ? 'FLAG' : 'PASS'
// Attach machine-readable result for the matrix reporter.
await testInfo.attach('branded-result', {
contentType: 'application/json',
body: Buffer.from(JSON.stringify({
id: s.id, category: s.category, brand: s.brand, url: s.url, bp, posture: s.posture,
cellState, checks, flagged, unexpected: unexpected.map((u) => ({ name: u.name, detail: u.detail })),
staleFlags, accentNote: accNote, bodyForeign: p.bodyForeign,
rootKids: p.rootKids, bodyLen: p.bodyLen, bodySample: p.bodySample,
})),
})
// Console breadcrumb for the list reporter.
const glyph = cellState === 'PASS' ? '✓' : cellState === 'FLAG' ? '⚑' : '✗'
console.log(` ${glyph} ${s.id} @${bp} [${cellState}] ${checks.filter((c) => c.ok).length}/8 checks · ${accNote}` +
(flagged.length ? ` · FLAG: ${flagged.map((f) => f.check).join(',')}` : '') +
(unexpected.length ? ` · REGRESSION: ${unexpected.map((u) => `${u.name}(${u.detail})`).join(' ; ')}` : '') +
(staleFlags.length ? ` · STALE-FLAG(now passing): ${staleFlags.join(',')}` : '') +
(p.bodyForeign.length ? ` · body-ref: ${p.bodyForeign.join(',')}` : ''))
// ── The gate: only UNEXPECTED failures fail the run (documented flags don't) ─
expect(
unexpected,
`${s.id} @${bp} — REGRESSION in un-flagged check(s):\n` +
unexpected.map((u) => ` • ${u.name}: ${u.detail}`).join('\n') +
`\n(if this is a newly-accepted state, add a flag in e2e/branded/surfaces.ts with a flip condition)`,
).toEqual([])
})
})
}