import { expect, test } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.route('**/api/v1/public/**', async (route) => { const path = new URL(route.request().url()).pathname; if (path.includes('/lookups/')) return route.fulfill({ json: { data: [] } }); if (path.endsWith('/search')) return route.fulfill({ json: { data: { items: [{ id: 'listing-1', productName: 'Pikachu ex', askingPriceMinor: 2500, status: 'AVAILABLE' }], pagination: { page: 1, limit: 20, total: 1, totalPages: 1 }, } } }); if (path.endsWith('/missing')) return route.fulfill({ status: 404, json: {} }); return route.fulfill({ json: { data: { id: 'listing-1', name: 'Pikachu ex', productName: 'Pikachu ex', askingPriceMinor: 2500, status: 'AVAILABLE', images: [], sellerDisplayName: 'Pika Collectibles', sellerContactEmail: 'collector@example.test', } } }); }); }); test('keyboard navigation retains catalog filters, titles and entry focus', async ({ page }) => { await page.goto('/?q=Pikachu&status=AVAILABLE'); await expect(page).toHaveTitle('Catalog | Pokémon Inventory'); await expect(page.getByRole('main')).toBeFocused(); const search = page.getByRole('searchbox', { name: 'Search the catalog' }); await page.keyboard.press('Tab'); await expect(search).toBeFocused(); await search.fill('Pikachu ex'); await expect(page).toHaveURL(/q=Pikachu(?:\+|%20)ex/); await expect(search).toBeFocused(); const catalogUrl = page.url(); const card = page.getByRole('link', { name: /Pikachu ex/ }); await card.focus(); await page.keyboard.press('Enter'); await expect(page).toHaveTitle('Pikachu ex | Pokémon Inventory'); await expect(page.getByRole('main')).toBeFocused(); await page.keyboard.press('Tab'); const back = page.getByRole('link', { name: 'Back to catalog' }); await expect(back).toBeFocused(); await page.keyboard.press('Enter'); await expect(page).toHaveURL(catalogUrl); await expect(search).toHaveValue('Pikachu ex'); await expect(page.getByRole('main')).toBeFocused(); await page.goBack(); await expect(page).toHaveTitle('Pikachu ex | Pokémon Inventory'); await expect(page.getByRole('main')).toBeFocused(); await page.goBack(); await expect(page).toHaveURL(catalogUrl); await expect(search).toHaveValue('Pikachu ex'); }); test('direct product and unavailable pages have titles, focus and safe return links', async ({ page }) => { await page.goto('/products/pikachu?from=https://example.test'); await expect(page).toHaveTitle('Pikachu ex | Pokémon Inventory'); await expect(page.getByRole('main')).toBeFocused(); await expect(page.getByRole('link', { name: 'Back to catalog' })).toHaveAttribute('href', '/'); await page.goto('/listings/missing'); await expect(page).toHaveTitle('Listing not found. | Pokémon Inventory'); await expect(page.getByRole('alert')).toHaveText('Listing not found.'); await expect(page.getByRole('main')).toBeFocused(); }); test('late detail results do not steal focus from the return link', async ({ page }) => { let release; const responseReady = new Promise((resolve) => { release = resolve; }); await page.route('**/api/v1/public/products/slow', async (route) => { await responseReady; await route.fulfill({ json: { data: { name: 'Slow Pikachu', images: [] } } }); }); await page.goto('/products/slow'); await expect(page.getByRole('status')).toHaveText('Loading product...'); await page.keyboard.press('Tab'); const back = page.getByRole('link', { name: 'Back to catalog' }); await expect(back).toBeFocused(); release(); await expect(page).toHaveTitle('Slow Pikachu | Pokémon Inventory'); await expect(back).toBeFocused(); });