- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
151 lines
6.5 KiB
TypeScript
151 lines
6.5 KiB
TypeScript
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()
|
|
})
|
|
})
|