test(e2e): add exported-page a11y smoke (focus outline, skip-link tab order) #4
@@ -144,6 +144,13 @@
|
||||
- CI verification note: 러너 온라인 이후, 사소 변경으로 CI 트리거 검증 예정
|
||||
- CI rerun: 워크플로 재실행용 ping 커밋
|
||||
|
||||
### PR/CI 결과(2025-11-14)
|
||||
- PR: feat/ci-verify-run → main 머지 완료
|
||||
- CI: Vitest + Playwright 전부 그린
|
||||
- E2E 추가: Exported page skip-link focus 표시 회귀 테스트 포함
|
||||
- 워크플로: self-hosted runner [macos, host], Playwright 보고서 업로드 아티팩트 활성화
|
||||
- 제안: 다음 릴리스 태그(v0.2.0)로 회귀 커버리지/Benefits 섹션 추가 반영
|
||||
|
||||
#### 결과: CI 통과 (self-hosted Gitea Actions)
|
||||
- PR: https://gitea.jaybe.dev/jaybe/landing-builder/pulls/2
|
||||
- 러너: self-hosted, labels: [macos, host]
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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 <main>
|
||||
// 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()
|
||||
})
|
||||
Reference in New Issue
Block a user