- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation. - Changed default base URL for Playwright tests and updated security headers to reflect the new branding. - Enhanced README and API documentation to include new authentication endpoints and product access details. This refactor aligns the project branding and improves clarity in the API documentation.
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: /SolaceScan/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()
|
|
})
|
|
})
|