import { expect, test, type Page } from '@playwright/test' const EXPLORER_URL = process.env.EXPLORER_URL || 'https://explorer.d-bis.org' const ADDRESS_TEST = '0x99b3511a2d315a497c8112c1fdd8d508d4b1e506' async function expectHeading(page: Page, name: RegExp) { await expect(page.getByRole('heading', { name })).toBeVisible({ timeout: 10000 }) } function collectUnexpectedConsoleErrors(page: Page, allowlist: RegExp[] = []) { const unexpectedErrors: string[] = [] page.on('console', (message) => { if (message.type() !== 'error') { return } const text = message.text() if (allowlist.some((pattern) => pattern.test(text))) { return } unexpectedErrors.push(text) }) return async () => { expect(unexpectedErrors).toEqual([]) } } test.describe('Explorer Frontend - Route Coverage', () => { for (const route of [ { path: '/', heading: /SolaceScanScout/i }, { path: '/blocks', heading: /^Blocks$/i }, { path: '/transactions', heading: /^Transactions$/i }, { path: '/addresses', heading: /^Addresses$/i }, { path: '/watchlist', heading: /^Watchlist$/i }, { path: '/pools', heading: /^Pools$/i }, { path: '/liquidity', heading: /Public liquidity, route discovery, and execution access points/i }, { path: '/wallet', heading: /Wallet & MetaMask/i }, { path: '/tokens', heading: /^Tokens$/i }, { path: '/search', heading: /^Search$/i }, ]) { test(`${route.path} loads`, async ({ page }) => { await page.goto(`${EXPLORER_URL}${route.path}`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page).toHaveURL(new RegExp(route.path === '/' ? '/?$' : route.path.replace('/', '\\/')), { timeout: 8000 }) await expectHeading(page, route.heading) }) } test('/addresses/:address loads address detail', async ({ page }) => { await page.goto(`${EXPLORER_URL}/addresses/${ADDRESS_TEST}`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expectHeading(page, /Address/i) await expect(page.getByText(/Back to addresses/i)).toBeVisible({ timeout: 10000 }) }) }) test.describe('Explorer Frontend - Current Navigation', () => { test('global shell is present on both app and pages routes', async ({ page }) => { await page.goto(`${EXPLORER_URL}/wallet`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page.getByRole('link', { name: /Go to explorer home/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByText(/Support:/i)).toBeVisible({ timeout: 10000 }) await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page.getByRole('link', { name: /Go to explorer home/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByText(/Support:/i)).toBeVisible({ timeout: 10000 }) }) test('wallet page exposes the current Chain 138 Snap action', async ({ page }) => { await page.goto(`${EXPLORER_URL}/wallet`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page.getByRole('button', { name: /Install Open Snap/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByText(/Chain 138 Open Snap/i)).toBeVisible({ timeout: 10000 }) }) test('blocks list links to a current block detail route', async ({ page }) => { await page.goto(`${EXPLORER_URL}/blocks`, { waitUntil: 'networkidle', timeout: 20000 }) const blockLink = page.locator('a[href^="/blocks/"]').first() await expect(blockLink).toBeVisible({ timeout: 10000 }) await blockLink.click() await expect(page).toHaveURL(/\/blocks\/\d+$/, { timeout: 10000 }) await expect(page.getByText(/Back to blocks/i)).toBeVisible({ timeout: 10000 }) }) test('transactions list links to a current transaction detail route', async ({ page }) => { await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'networkidle', timeout: 20000 }) const transactionLinks = page.locator('a[href^="/transactions/"]') const transactionCount = await transactionLinks.count() if (transactionCount === 0) { await expect(page.getByText(/Recent transactions are unavailable right now/i)).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('button', { name: /^Next$/i })).toBeDisabled() return } const href = await transactionLinks.first().getAttribute('href') expect(href).toMatch(/^\/transactions\/0x[a-f0-9]+$/i) await page.goto(`${EXPLORER_URL}${href}`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page).toHaveURL(/\/transactions\/0x[a-f0-9]+$/i, { timeout: 10000 }) await expect(page.getByText(/Back to transactions/i)).toBeVisible({ timeout: 10000 }) }) test('addresses page opens a current address detail route', async ({ page }) => { await page.goto(`${EXPLORER_URL}/addresses`, { waitUntil: 'networkidle', timeout: 20000 }) await page.getByPlaceholder('0x...').fill(ADDRESS_TEST) await page.getByRole('button', { name: /Open address/i }).click() await expect(page).toHaveURL(new RegExp(`/addresses/${ADDRESS_TEST}$`, 'i'), { timeout: 10000 }) await expect(page.getByText(/Back to addresses/i)).toBeVisible({ timeout: 10000 }) }) test('homepage keeps recent blocks visible when stats are temporarily unavailable', async ({ page }) => { const assertConsole = collectUnexpectedConsoleErrors(page, [ /api\/v2\/stats/i, /503/i, /service unavailable/i, ]) await page.route(`${EXPLORER_URL}/api/v2/stats`, async (route) => { await route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ error: 'service_unavailable' }), }) }) await page.route(new RegExp(`${EXPLORER_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/api/v2/blocks\\?.*`), async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ items: [ { hash: '0xabc', height: 4321, timestamp: '2026-04-05T00:00:00.000Z', miner: { hash: '0xdef' }, transaction_count: 7, gas_used: 21000, gas_limit: 30000000, }, ], }), }) }) await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page.getByText(/Live network stats are temporarily unavailable/i)).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('link', { name: /Block #4321/i })).toBeVisible({ timeout: 10000 }) await assertConsole() }) })