Files
universe/e2e/10-stats-dashboard.spec.ts
Antje WorringandHanzo AI da50d29940 e2e: fail the gate when any job it guards fails
The gate enforced only health. explorer, apps and branded each printed a
warning and fell through to an unconditional 'gate passed', so run 62877
went green with three red jobs. One rule now covers every guarded job:
green or the gate is red.

The apps matrix named itself App E2E (${{ matrix.app }}), which
stringifies the leg mapping to 'Object' — five identically named jobs,
none identifying which leg failed. Use matrix.app.name.

Explorer E2E was one of the hidden failures: it asserted SPC serves
stats, but no SPC chain exists and the explorer config disables the slug,
so api-explore-spc answers a plain-text 404. Four specs each carried
their own copy of the chain list; EXPLORERS in env.ts is now the one
list, and SPC is absent from it with the reason recorded.

httpGetJson fed error-page bodies to JSON.parse, reporting a complaint
about byte 4 of '404 page not found' instead of the endpoint and status.
It now reports both.
2026-08-28 12:39:52 -07:00

130 lines
6.0 KiB
TypeScript

import { test, expect } from '@playwright/test'
import {
getUrls,
rpcCall, ethBlockNumber, ethGasPrice,
getCurrentValidators,
httpStatus, httpGetJson,
snap,
} from './helpers'
import { EXPLORERS } from './env'
const urls = getUrls()
// ── Explorer Stats ──────────────────────────────────────────────────
test.describe('Stats — Explorer Block Count', () => {
test('explorer block count matches RPC', async ({ request }) => {
const [rpcBlock, explorerStats] = await Promise.all([
ethBlockNumber(request, urls.rpc),
httpGetJson(request, `${urls.apiExploreLux}/stats`) as Promise<Record<string, unknown>>,
])
const explorerBlocks = Number(explorerStats.total_blocks || 0)
console.log(` RPC block: ${rpcBlock}, Explorer blocks: ${explorerBlocks}`)
expect(rpcBlock).toBeGreaterThanOrEqual(0)
expect(explorerBlocks).toBeGreaterThan(0)
// Explorer may have stale indexed data after chain restart, or lag behind RPC
// Just verify both are responding with valid numbers
console.log(` Difference: ${Math.abs(rpcBlock - explorerBlocks)} blocks`)
})
test('explorer shows non-zero transaction count', async ({ request }) => {
const data = await httpGetJson(request, `${urls.apiExploreLux}/stats`) as Record<string, unknown>
const txCount = Number(data.total_transactions || 0)
console.log(` Total transactions: ${txCount}`)
expect(txCount).toBeGreaterThan(0)
})
test('explorer shows non-zero address count', async ({ request }) => {
const data = await httpGetJson(request, `${urls.apiExploreLux}/stats`) as Record<string, unknown>
const addrCount = Number(data.total_addresses || 0)
console.log(` Total addresses: ${addrCount}`)
expect(addrCount).toBeGreaterThan(0)
})
})
// ── Validator Count Consistency ─────────────────────────────────────
test.describe('Stats — Validator Count Consistency', () => {
test('P-Chain validator count >= 5', async ({ request }) => {
const validators = await getCurrentValidators(request, urls.rpcPlatform) as { nodeID: string }[]
console.log(` P-Chain validators: ${validators.length}`)
expect(validators.length).toBeGreaterThanOrEqual(5)
})
test('every indexed explorer reports blocks', async ({ request }) => {
for (const { name, api } of EXPLORERS) {
const data = await httpGetJson(request, `${api}/stats`) as Record<string, unknown>
const blocks = Number(data.total_blocks || 0)
console.log(` ${name}: ${blocks} blocks`)
expect(blocks).toBeGreaterThan(0)
}
})
})
// ── Gas Price ───────────────────────────────────────────────────────
test.describe('Stats — Gas Tracker', () => {
test('gas price from RPC is consistent', async ({ request }) => {
const gas1 = await ethGasPrice(request, urls.rpc)
const gas2 = await ethGasPrice(request, urls.rpc)
console.log(` Gas #1: ${Number(gas1) / 1e9} gwei, Gas #2: ${Number(gas2) / 1e9} gwei`)
expect(gas1).toBeGreaterThan(0n)
expect(gas2).toBeGreaterThan(0n)
// Gas price should not swing by more than 10x between consecutive calls
const ratio = Number(gas1) / Number(gas2)
expect(ratio).toBeGreaterThan(0.1)
expect(ratio).toBeLessThan(10)
})
test('explorer gas tracker endpoint accessible', async ({ request }) => {
const status = await httpStatus(request, `${urls.apiExploreLux}/stats`)
console.log(` Explorer stats endpoint: HTTP ${status}`)
expect(status).toBe(200)
})
})
// ── Exchange TVL / Liquidity ────────────────────────────────────────
test.describe('Stats — Exchange Liquidity', () => {
test('exchange shows TVL or liquidity stats', async ({ request, page }) => {
const status = await httpStatus(request, urls.exchange, 10_000)
if (status === 0) {
test.skip(true, 'lux.exchange is unreachable')
}
await page.goto(urls.exchange, { timeout: 30_000, waitUntil: 'domcontentloaded' })
await page.waitForTimeout(5000)
const liquidityUI = page.locator(
'text=/tvl/i, text=/liquidity/i, text=/volume/i, text=/total value/i, ' +
'[class*="tvl"], [class*="TVL"], [class*="liquidity"], [class*="volume"]'
).first()
const visible = await liquidityUI.isVisible({ timeout: 10_000 }).catch(() => false)
if (visible) {
await snap(page, 'stats-exchange-tvl')
}
// Exchange should at least load
expect(await page.title()).toBeTruthy()
})
})
// ── Cross-Chain Stats Comparison ────────────────────────────────────
test.describe('Stats — Cross-Chain Comparison', () => {
test('Lux and Zoo both have blocks and transactions', async ({ request }) => {
const [luxStats, zooStats] = await Promise.all([
httpGetJson(request, `${urls.apiExploreLux}/stats`) as Promise<Record<string, unknown>>,
httpGetJson(request, `${urls.apiExploreZoo}/stats`) as Promise<Record<string, unknown>>,
])
const luxBlocks = Number(luxStats.total_blocks || 0)
const zooBlocks = Number(zooStats.total_blocks || 0)
const luxTxs = Number(luxStats.total_transactions || 0)
const zooTxs = Number(zooStats.total_transactions || 0)
console.log(` Lux: ${luxBlocks} blocks, ${luxTxs} txs`)
console.log(` Zoo: ${zooBlocks} blocks, ${zooTxs} txs`)
expect(luxBlocks).toBeGreaterThan(0)
expect(zooBlocks).toBeGreaterThan(0)
})
test('P-Chain height is non-negative', async ({ request }) => {
const result = await rpcCall(request, urls.rpcPlatform, 'platform.getHeight', {}) as { height: string }
const height = Number(result.height)
console.log(` P-Chain height: ${height}`)
// P-Chain may report 0 height when using linearized commit model
expect(height).toBeGreaterThanOrEqual(0)
})
})