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.
130 lines
6.0 KiB
TypeScript
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)
|
|
})
|
|
})
|