Files
universe/e2e/61-console.spec.ts
zeekayandHanzo Dev eb5176cc01 test(e2e): let the economy screens finish reading the chain before judging them
The nine screens the owner named read the chain, and the render check clears at
2.5s. MEASURED on /dashboard/treasury at 3/8/15/25/40s: at 2.5s it still shows
"…" and "Reading token balances…", and it has settled by 3s — no permanent
skeleton, just a check that was looking too early. So the evidence screenshots
were of a half-loaded page and the chain-context assertion was reading an
ellipsis where the address had not arrived yet.

One 3s settle in the economy block only. It does not soften the assertion —
/dashboard/{safes,mpc,bridge,dex,deploy} still show no network, chainId,
address or height at 3/3 viewports.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 20:55:45 -07:00

177 lines
9.3 KiB
TypeScript

import { test, expect, Page } from '@playwright/test'
import { env } from './env'
import { CLOUD_DASHBOARD, CLOUD_ECONOMY, CLOUD_PUBLIC } from './routes'
import { expectRendered, renderRoute, signIn, snap } from './helpers'
// ---------------------------------------------------------------------------
// THE ADMIN DASH, SIGNED IN — all 44 dashboard routes
// ---------------------------------------------------------------------------
// lux.cloud ships 44 routes under /dashboard and, until 51-institutional-
// baseline landed this morning, not one of them had ever been opened by this
// suite with a session. Everything green was anonymous: the SPA shell answers
// 200 for /dashboard/zzz-bogus, so anonymous coverage of a dashboard route
// proves the router exists and nothing else.
//
// This is the sweep that opens all of them as the owner. 51-institutional-
// baseline is the deep read of 27 screens against the six institutional
// criteria and writes a scored report; this is the BREADTH pass — every route,
// three viewports, does it render as a signed-in page. Two different questions,
// two files, no overlap: that one grades, this one enumerates.
//
// SESSION: signed in once per project (viewport) via helpers.signIn(), which is
// the same flow 50-login proves end to end. There is no second copy of the
// click path here — the second copy is how a run ends up somewhere else
// entirely when a button's accessible name changes.
//
// NO CREDENTIAL, NO GREEN. If LUX_E2E_PASSWORD is unset this whole file FAILS
// rather than skipping. A skipped authenticated sweep reports the same colour
// as a passing one, and the entire point of the file is that nobody had checked.
//
// SAFETY: read-only. Every screen here can move value — /dashboard/treasury,
// /dashboard/safes, /dashboard/mpc, /dashboard/bridge. This spec navigates and
// screenshots. It submits no form, prepares no transaction, signs nothing,
// broadcasts nothing, and re-keys nothing. Those paths are owner-gated and this
// suite must never be the thing that trips one.
// NOT `mode: 'serial'`. Serial skips every remaining test in the file the
// moment one fails, which for a 60-route sweep means one broken screen hides
// the state of the other fifty-nine — measured: the first run stopped at
// /dashboard/safes and never reported the eight economy screens behind it. The
// file already runs in one worker in file order (fullyParallel:false), so the
// shared signed-in page works without it, and every route now gets a verdict.
const CLOUD = env.urls.cloud
const CLOUD_HOST = new URL(CLOUD).host
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const vp = testInfo.project.use.viewport ?? undefined
const ctx = await browser.newContext({ viewport: vp, ignoreHTTPSErrors: true })
page = await ctx.newPage()
await signIn(page)
console.log(` signed in as ${env.urls.cloud} → ${page.url()}`)
})
test.afterAll(async () => {
await page?.context().close()
})
// ── The session is real ────────────────────────────────────────────────────
// Asserted with POSITIVE signals only. Two false passes today came from
// checking that an error string was absent; a page that never rendered has no
// error string on it either.
test.describe('Session', () => {
test('the browser is signed in and on lux.cloud, not the IdP', async () => {
await renderRoute(page, `${CLOUD}/dashboard`)
const host = new URL(page.url()).host
console.log(` landed on ${page.url()}`)
expect(host, `still on the identity provider — the code→token exchange did not complete`)
.toBe(CLOUD_HOST)
// Any ONE of these is proof of a signed-in shell. All three are measured
// present on lux.cloud/dashboard; requiring all three would make the test
// track the nav's layout instead of the session.
const signals = {
signOut: await page.getByText(/sign out|log out/i).first().isVisible({ timeout: 10_000 }).catch(() => false),
nodes: await page.getByText(/nodes & validators|validators/i).first().isVisible({ timeout: 5_000 }).catch(() => false),
chains: await page.getByText(/chains/i).first().isVisible({ timeout: 5_000 }).catch(() => false),
}
console.log(` signals: ${JSON.stringify(signals)}`)
expect(Object.values(signals).some(Boolean),
`no signed-in signal on ${page.url()} — expected "Sign out", "Nodes & Validators" or `
+ `"Chains" and found none of them`).toBe(true)
await snap(page, 'console-session')
})
})
// ── Every dashboard route ──────────────────────────────────────────────────
test.describe(`Console — ${CLOUD_DASHBOARD.length} dashboard routes`, () => {
for (const route of CLOUD_DASHBOARD) {
const name = route.replace(/^\/dashboard\/?/, '') || 'overview'
test(`${route}`, async () => {
const r = await expectRendered(page, CLOUD + route, `signed-in ${route}`)
// Still signed in — a route that silently bounces to the IdP would
// otherwise "render" perfectly and pass.
expect(new URL(r.url).host,
`${route} bounced the session to ${r.url}`).toBe(CLOUD_HOST)
// A permanent skeleton is worse than an empty state: it never resolves to
// either answer, so an operator cannot tell "no data" from "broken". Give
// it a further 6s, then require it to have settled.
const stillLoading = async () => await page
.locator('[class*="skeleton"], [class*="Skeleton"], [aria-busy="true"]')
.first().isVisible({ timeout: 1_000 }).catch(() => false)
if (await stillLoading()) {
await page.waitForTimeout(6_000)
expect(await stillLoading(),
`${route} is still showing loading skeletons ~9s after load — it never resolves to `
+ `data OR to an empty state`).toBe(false)
}
console.log(` ${route.padEnd(42)} ${String(r.textLen).padStart(6)} chars `
+ `${r.heading ? `“${r.heading}”` : '(no heading)'}`)
await snap(page, `console-${name.replace(/\//g, '-')}`)
})
}
})
// ── The economy screens, held to a higher bar ──────────────────────────────
// The owner's ask, verbatim: "manage bridge fees; wallets; mpc; treasury deploy
// and see all contracts safe, nodes explorer exchange bridge shit all that jazz
// and adjust and manage all of our economy". These nine screens ARE that ask.
// Rendering is not enough for them — an institutional operator has to be able
// to tell, on the screen, WHICH network and WHICH state they are looking at
// before they touch anything. A treasury page with no chain context is how
// somebody signs on the wrong network.
test.describe('Economy screens — the owner\'s named surfaces', () => {
for (const route of CLOUD_ECONOMY) {
test(`${route} names what it is and which chain it is on`, async () => {
const r = await expectRendered(page, CLOUD + route, `economy screen ${route}`)
// These nine screens read the chain, so give them a beat past the 2.5s
// paint check before judging what is on them. MEASURED: /dashboard/treasury
// shows "…" and "Reading token balances…" at 2.5s and has settled by 3s
// (checked at 3/8/15/25/40s — no permanent skeleton). Without this the
// owner's evidence screenshots are of a half-loaded page and the context
// check below reads an ellipsis instead of an address.
await page.waitForTimeout(3_000)
// Says what it is.
expect(r.heading.length,
`${route} renders with no heading — an operator cannot tell what screen they are on`)
.toBeGreaterThan(0)
// Says which chain / network / address it is talking about. Any of a
// network name, a chainId, a block height or a 0x address counts; a
// value-bearing screen with none of them has no trust signal at all.
const body = (await page.textContent('body')) ?? ''
const context = {
network: /\b(mainnet|testnet|devnet)\b/i.test(body),
chainId: new RegExp(`\\b${env.chainIds.lux}\\b`).test(body),
address: /0x[a-fA-F0-9]{6,}/.test(body),
height: /\b\d{1,3}(,\d{3})+\b/.test(body),
}
console.log(` ${route.padEnd(28)} “${r.heading}” context=${JSON.stringify(context)}`)
expect(Object.values(context).some(Boolean),
`${route} shows no network, chainId, address or height — nothing on this screen tells `
+ `an operator which chain they are about to act on`).toBe(true)
await snap(page, `economy-${route.replace(/^\/dashboard\//, '').replace(/\//g, '-')}`)
})
}
})
// ── Sign-out is reachable ──────────────────────────────────────────────────
// Not exercised — clicking it would end the session for the serial file. Its
// PRESENCE is the assertion: an institutional console that cannot be left is a
// finding of its own.
test.describe('Public routes still serve with a session', () => {
for (const route of CLOUD_PUBLIC) {
test(`${route} renders for a signed-in browser`, async () => {
const r = await expectRendered(page, CLOUD + route, `signed-in public ${route}`)
console.log(` ${route.padEnd(12)} ${r.textLen} chars`)
})
}
})