A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Accessibility', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
});
|
|
|
|
test('skip link is focusable and works', async ({ page }) => {
|
|
// Tab to focus skip link
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Skip link should be visible when focused
|
|
const skipLink = page.getByRole('link', { name: /skip to main content/i });
|
|
await expect(skipLink).toBeFocused();
|
|
await expect(skipLink).toBeVisible();
|
|
|
|
// Click skip link
|
|
await skipLink.click();
|
|
|
|
// Focus should move to main content
|
|
const main = page.locator('#main-content');
|
|
await expect(main).toBeFocused();
|
|
});
|
|
|
|
test('all interactive elements are keyboard accessible', async ({ page }) => {
|
|
// Tab through the page and ensure focusable elements receive focus
|
|
let focusedElements = 0;
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
await page.keyboard.press('Tab');
|
|
const focused = await page.evaluate(() => document.activeElement?.tagName);
|
|
if (focused && ['A', 'BUTTON', 'INPUT'].includes(focused)) {
|
|
focusedElements++;
|
|
}
|
|
}
|
|
|
|
// Should have multiple focusable elements
|
|
expect(focusedElements).toBeGreaterThan(5);
|
|
});
|
|
|
|
test('pagination has correct aria attributes', async ({ page }) => {
|
|
await page.goto('/blocks');
|
|
|
|
// Check pagination nav has correct role
|
|
const pagination = page.getByRole('navigation', { name: /pagination/i });
|
|
await expect(pagination).toBeVisible();
|
|
|
|
// Current page should have aria-current
|
|
const currentPage = page.locator('[aria-current="page"]');
|
|
await expect(currentPage).toBeVisible();
|
|
});
|
|
|
|
test('copy buttons have proper labels', async ({ page }) => {
|
|
await page.goto('/blocks');
|
|
|
|
// Click first block to go to detail
|
|
const firstBlock = page.locator('a[href^="/block/"]').first();
|
|
await firstBlock.click();
|
|
|
|
// Copy button should have accessible label
|
|
const copyButton = page.getByRole('button', { name: /copy/i }).first();
|
|
await expect(copyButton).toBeVisible();
|
|
await expect(copyButton).toHaveAttribute('aria-label', /copy/i);
|
|
});
|
|
|
|
test('connection status announces changes', async ({ page }) => {
|
|
// Connection status should have aria-live
|
|
const connectionStatus = page.locator('[aria-live="polite"]').filter({ hasText: /live|offline|connecting/i });
|
|
await expect(connectionStatus).toBeVisible();
|
|
await expect(connectionStatus).toHaveAttribute('aria-live', 'polite');
|
|
});
|
|
});
|