From 1ffebc774f158b39f74690c43a144783fd241b0d Mon Sep 17 00:00:00 2001 From: Jaybe Date: Fri, 14 Nov 2025 20:29:54 +0900 Subject: [PATCH] test(e2e): add exported-page a11y smoke (focus outline, skip-link tab order) --- tests/e2e/export-focus-outline.e2e.ts | 42 +++++++++++++++++++++++ tests/e2e/export-tab-order.e2e.ts | 49 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 tests/e2e/export-focus-outline.e2e.ts create mode 100644 tests/e2e/export-tab-order.e2e.ts diff --git a/tests/e2e/export-focus-outline.e2e.ts b/tests/e2e/export-focus-outline.e2e.ts new file mode 100644 index 0000000..7f611fa --- /dev/null +++ b/tests/e2e/export-focus-outline.e2e.ts @@ -0,0 +1,42 @@ +import { test, expect } from '@playwright/test' + +type WindowWithExport = Window & { + __exportPreview?: () => { html: string; css: string; js: string } +} + +const builderUrl = '/en/builder' + +// Verify that focus outline tokens from CSS are applied in a real browser context +// by focusing the submit button in the exported page and inspecting computed styles. +test('Exported page shows 3px solid outline color on focused button', async ({ page, context }) => { + await page.goto(builderUrl) + // Minimal content to export + await page.getByTestId('card-hero').click() + + await page.waitForFunction(() => Boolean((window as unknown as WindowWithExport).__exportPreview), { timeout: 5000 }) + const exported = await page.evaluate(() => (window as unknown as WindowWithExport).__exportPreview?.() ?? null) + expect(exported).not.toBeNull() + + const preview = await context.newPage() + await preview.setContent(exported!.html) + await preview.addStyleTag({ content: exported!.css }) + + const submit = preview.locator('button[type="submit"]') + await expect(submit).toHaveCount(1) + await submit.focus() + + // Read computed style in the page context + const style = await preview.evaluate(() => { + const el = document.querySelector('button[type="submit"]') as HTMLElement | null + if (!el) return null + const cs = window.getComputedStyle(el) + return { outlineWidth: cs.outlineWidth, outlineStyle: cs.outlineStyle, outlineColor: cs.outlineColor } + }) + expect(style).not.toBeNull() + // Expect 3px solid and brand color (#2563eb => rgb(37, 99, 235)) + expect(style!.outlineWidth).toBe('3px') + expect(style!.outlineStyle).toBe('solid') + expect(style!.outlineColor.replace(/\s+/g, '')).toMatch(/rgb\(37,99,235\)|#2563eb/i) + + await preview.close() +}) diff --git a/tests/e2e/export-tab-order.e2e.ts b/tests/e2e/export-tab-order.e2e.ts new file mode 100644 index 0000000..7a1e26b --- /dev/null +++ b/tests/e2e/export-tab-order.e2e.ts @@ -0,0 +1,49 @@ +import { test, expect } from '@playwright/test' + +type WindowWithExport = Window & { + __exportPreview?: () => { html: string; css: string; js: string } +} + +const builderUrl = '/en/builder' + +// Verify tab order after activating the skip-link: focus should move into
+// Then the first Tab should reach a focusable descendant (e.g., form submit button) +test('Skip-link Enter moves focus into main; next Tab reaches first focusable', async ({ page, context }) => { + await page.goto(builderUrl) + await page.getByTestId('card-hero').click() + + await page.waitForFunction(() => Boolean((window as unknown as WindowWithExport).__exportPreview), { timeout: 5000 }) + const exported = await page.evaluate(() => (window as unknown as WindowWithExport).__exportPreview?.() ?? null) + expect(exported).not.toBeNull() + + const preview = await context.newPage() + await preview.setContent(exported!.html) + await preview.addStyleTag({ content: exported!.css }) + + const skip = preview.locator('a.skip-link') + await expect(skip).toHaveCount(1) + await skip.focus() + await expect(skip).toBeFocused() + + // Activate skip-link + await preview.keyboard.press('Enter') + + // Focus should be inside #main or on #main + const focusedInMain = await preview.evaluate(() => { + const main = document.getElementById('main')! + const active = document.activeElement as HTMLElement | null + if (!active) return false + return active === main || main.contains(active) + }) + expect(focusedInMain).toBeTruthy() + + // Press Tab and expect a focusable inside main (submit button) to receive focus + await preview.keyboard.press('Tab') + const isSubmitFocused = await preview.evaluate(() => { + const active = document.activeElement as HTMLElement | null + return !!active && active.tagName.toLowerCase() === 'button' && (active as HTMLButtonElement).type === 'submit' + }) + expect(isSubmitFocused).toBeTruthy() + + await preview.close() +})