import { hasFocusVisible } from "../../fixtures/accessibility"; import { SELECTORS } from "../../fixtures/selectors"; import { expect, test } from "../../fixtures/test"; test.describe("Keyboard Navigation @smoke", () => { test.beforeEach(async ({ gotoApp, waitForAppReady }) => { await gotoApp(); await waitForAppReady(); }); test("skip link is first focusable element", async ({ page }) => { // Press Tab to focus skip link await page.keyboard.press("Tab"); // Skip link should be focused const skipLink = page.locator(SELECTORS.skipLink); await expect(skipLink).toBeFocused(); // Skip link should be visible when focused const isVisible = await skipLink.isVisible(); expect(isVisible).toBe(true); }); test("skip link navigates to main content", async ({ page }) => { // Focus and activate skip link await page.keyboard.press("Tab"); await page.keyboard.press("Enter"); // Main content should be focused const mainContent = page.locator("#main-content"); await expect(mainContent).toBeFocused(); }); test("header controls are keyboard accessible @smoke", async ({ page }) => { // Tab through header controls await page.keyboard.press("Tab"); // Skip link await page.keyboard.press("Tab"); // Logo await page.keyboard.press("Tab"); // Language select const languageSelect = page.locator(SELECTORS.header.languageSelect); await expect(languageSelect).toBeFocused(); await page.keyboard.press("Tab"); // Theme menu button const themeButton = page.locator(SELECTORS.header.themeMenuButton); await expect(themeButton).toBeFocused(); }); test("theme menu is keyboard operable", async ({ page }) => { // Navigate to theme button await page.keyboard.press("Tab"); // Skip link await page.keyboard.press("Tab"); // Logo await page.keyboard.press("Tab"); // Language select await page.keyboard.press("Tab"); // Theme button // Open theme menu with Enter await page.keyboard.press("Enter"); // Menu should be visible const menu = page.locator(SELECTORS.themeMenu.root); await expect(menu).toBeVisible(); // Menu items should be focusable const menuItems = menu.locator('[role="menuitem"]'); const count = await menuItems.count(); expect(count).toBeGreaterThan(0); // Close menu with Escape await page.keyboard.press("Escape"); await expect(menu).not.toBeVisible(); }); test("hero read button is keyboard accessible @smoke", async ({ page, waitForHero, }) => { const hero = await waitForHero(); // Navigate to hero read button // Skip header controls first for (let i = 0; i < 4; i++) { await page.keyboard.press("Tab"); } // Hero read button should be focusable const readButton = hero.locator(SELECTORS.hero.readButton); // Check if button is in tab order by trying to focus it let found = false; for (let i = 0; i < 10; i++) { const activeElement = await page.evaluate( () => document.activeElement?.textContent?.trim() || document.activeElement?.getAttribute("aria-label"), ); if (activeElement?.includes("Read TL;DR")) { found = true; break; } await page.keyboard.press("Tab"); } expect(found).toBe(true); }); test("feed articles are keyboard navigable", async ({ page, waitForFeed, }) => { const feed = await waitForFeed(); // Get first article const firstArticle = feed.locator(SELECTORS.feed.articles).first(); // Source link should be keyboard accessible const sourceLink = firstArticle.locator(SELECTORS.feed.articleSource); const hasSourceLink = (await sourceLink.count()) > 0; if (hasSourceLink) { // Tab to source link let attempts = 0; let sourceLinkFocused = false; while (attempts < 20 && !sourceLinkFocused) { await page.keyboard.press("Tab"); const href = await page.evaluate(() => document.activeElement?.getAttribute("href"), ); if (href && href.startsWith("http")) { sourceLinkFocused = true; } attempts++; } expect(sourceLinkFocused).toBe(true); } // Read button should be keyboard accessible const readButton = firstArticle.locator(SELECTORS.feed.articleReadButton); let attempts = 0; let readButtonFocused = false; while (attempts < 30 && !readButtonFocused) { await page.keyboard.press("Tab"); const text = await page.evaluate(() => document.activeElement?.textContent?.trim(), ); if (text === "Read TL;DR") { readButtonFocused = true; } attempts++; } expect(readButtonFocused).toBe(true); }); test("focus-visible is shown on interactive elements", async ({ page, waitForHero, }) => { const hero = await waitForHero(); // Navigate to hero read button for (let i = 0; i < 4; i++) { await page.keyboard.press("Tab"); } // Find focused element const focusedElement = page.locator(":focus"); // Check that focused element has visible focus indicator const hasVisibleFocus = await hasFocusVisible(page, focusedElement); expect(hasVisibleFocus).toBe(true); }); test("footer links are keyboard accessible @smoke", async ({ page }) => { // Navigate to footer const footer = page.locator(SELECTORS.footer.root); await footer.scrollIntoViewIfNeeded(); // Tab through footer links let foundFooterLink = false; let attempts = 0; while (attempts < 50 && !foundFooterLink) { await page.keyboard.press("Tab"); const activeElement = await page.evaluate(() => document.activeElement); // Check if we're in footer const isInFooter = await page.evaluate(() => { const active = document.activeElement; const footer = document.querySelector("footer"); return footer?.contains(active); }); if (isInFooter) { foundFooterLink = true; } attempts++; } expect(foundFooterLink).toBe(true); }); }); test.describe("Focus Management", () => { test.beforeEach(async ({ gotoApp, waitForAppReady }) => { await gotoApp(); await waitForAppReady(); }); test("focus moves to modal when opened @smoke", async ({ page, waitForHero, }) => { const hero = await waitForHero(); // Click read button await hero.locator(SELECTORS.hero.readButton).click(); // Modal should be visible const modal = page.locator(SELECTORS.summaryModal.root); await expect(modal).toBeVisible(); // Focus should be inside modal const isFocusInModal = await page.evaluate(() => { const modal = document.querySelector('[role="dialog"]'); const active = document.activeElement; return modal?.contains(active); }); expect(isFocusInModal).toBe(true); }); test("focus is trapped within modal", async ({ page, waitForHero }) => { const hero = await waitForHero(); // Open modal await hero.locator(SELECTORS.hero.readButton).click(); // Tab multiple times for (let i = 0; i < 20; i++) { await page.keyboard.press("Tab"); // Check focus is still in modal const isInModal = await page.evaluate(() => { const modal = document.querySelector('[role="dialog"]'); const active = document.activeElement; return modal?.contains(active); }); expect(isInModal).toBe(true); } }); });