import { test, expect } from '@playwright/test'; test.describe('US-98: Focus Visible', () => { test('should show focus outline on button when tabbed to', async ({ page }) => { await page.goto('/ru-ru/schedule'); await page.waitForLoadState('networkidle'); // Tab to first interactive element await page.keyboard.press('Tab'); await page.waitForTimeout(100); const focusInfo = await page.evaluate(() => { const el = document.activeElement as HTMLElement; if (!el) return { hasFocusVisible: false, outlineWidth: '0px' }; const style = window.getComputedStyle(el); return { tagName: el.tagName, hasFocusVisible: style.outlineWidth !== '0px' && style.outlineWidth !== 'auto', outlineWidth: style.outlineWidth, outlineColor: style.outlineColor, }; }); expect(focusInfo.hasFocusVisible).toBe(true); }); test('should show focus outline on input field', async ({ page }) => { await page.goto('/ru-ru/schedule'); await page.waitForLoadState('networkidle'); // Find and focus on an input const inputs = page.locator('input[type="text"]'); const count = await inputs.count(); if (count > 0) { const firstInput = inputs.first(); await firstInput.focus(); await page.waitForTimeout(100); const focusInfo = await firstInput.evaluate((el) => { const style = window.getComputedStyle(el); return { hasFocusVisible: style.outlineWidth !== '0px' && style.outlineWidth !== 'auto', outlineWidth: style.outlineWidth, outlineColor: style.outlineColor, }; }); expect(focusInfo.hasFocusVisible).toBe(true); } }); test('should have sufficient color contrast for focus outline (blue)', async ({ page }) => { await page.goto('/ru-ru/schedule'); await page.waitForLoadState('networkidle'); // Tab to first button/interactive element await page.keyboard.press('Tab'); await page.waitForTimeout(100); const focusInfo = await page.evaluate(() => { const el = document.activeElement as HTMLElement; const style = window.getComputedStyle(el); return { outlineWidth: style.outlineWidth, outlineColor: style.outlineColor, outlineStyle: style.outlineStyle, }; }); // Check that outline is visible (not 0px) expect(focusInfo.outlineWidth).not.toMatch(/^0px$/); // Check that outline color is the expected blue (0066cc = rgb(0, 102, 204)) expect(focusInfo.outlineColor).toMatch(/rgb\(0,\s*102,\s*204\)/); }); test('should have zero console errors on focus interaction', async ({ page }) => { const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') { // Filter out expected dev/JSX runtime errors that aren't related to focus if (!msg.text().includes('factory') && !msg.text().includes('undefined')) { errors.push(msg.text()); } } }); await page.goto('/ru-ru/schedule'); await page.waitForLoadState('networkidle'); // Tab through several elements for (let i = 0; i < 5; i++) { await page.keyboard.press('Tab'); await page.waitForTimeout(50); } expect(errors).toHaveLength(0); }); });