บทที่ 14 · Part 3 — UI and API Capabilities

Accessibility, Visual and Emulation

ผสาน accessibility checks, screenshot assertions และ device emulation โดยรู้ขอบเขตของแต่ละหลักฐาน

Accessibility, Visual and Emulation

Functional test ที่คลิก Reserve ผ่าน mouse ได้อาจผ่านทั้งที่ keyboard focus เข้า dialog ไม่ได้ ส่วน screenshot ที่เหมือน baseline อาจยังไม่มี accessible name เครื่องมือ 3 กลุ่ม—semantic interaction, automated accessibility scan และ visual comparison—ให้หลักฐานคนละชนิดและแทนกันไม่ได้

จบบทนี้คุณจะ

ทดสอบ keyboard/ARIA semantics ใช้ axe และ ARIA snapshots อย่างมีขอบเขต สร้าง visual baseline ที่ review ได้ และเลือก browser/device/locale emulation ตาม product risk โดยไม่อ้างว่าเป็น real device

Accessibility Evidence 3 ชั้น

  1. Semantic interaction — role, accessible name, keyboard, focus, state
  2. Automated rules — axe scan หา violation ที่ rule engine ตรวจได้
  3. Manual evaluation — keyboard-only, zoom/reflow, screen reader และความเข้าใจ content

Playwright ระบุชัดว่า automated accessibility testing ตรวจพบปัญหาได้เพียงบางส่วน W3C ก็ต้องใช้ human judgement จึงห้ามสรุปว่า “ผ่าน WCAG” จาก scan สีเขียว ให้ report ว่า rules/scope/version ใดไม่พบ violation

Keyboard และ Dialog Semantics

test('A11Y-RESERVE-001 - keeps keyboard focus inside the reservation dialog', async ({ page }) => {
  await page.goto(`/workshops/${workshop.slug}`)
  await page.getByRole('button', { name: 'Reserve seat' }).focus()
  await page.keyboard.press('Enter')

  const dialog = page.getByRole('dialog', { name: 'Confirm reservation' })
  await expect(dialog).toBeVisible()
  await expect(dialog.getByRole('button', { name: 'Confirm' })).toBeFocused()

  await page.keyboard.press('Escape')
  await expect(dialog).toHaveCount(0)
  await expect(page.getByRole('button', { name: 'Reserve seat' })).toBeFocused()
})

Focus target และ Escape behavior ต้องมาจาก component/product contract กับ ARIA pattern ที่ใช้ ไม่ใช่ universal assumption ทุก dialog ดู WAI-ARIA dialog pattern

Automated Scan ด้วย axe

ติดตั้ง @axe-core/playwright แล้ว scope หน้า/state ที่รู้ว่าโหลดเสร็จ:

import AxeBuilder from '@axe-core/playwright'
import { expect, test } from '@playwright/test'

test('A11Y-CATALOG-001 - has no automatically detectable critical violations', async ({
  page,
}) => {
  await page.goto('/workshops')
  await expect(page.getByRole('heading', { name: 'Workshops' })).toBeVisible()

  const results = await new AxeBuilder({ page })
    .include('main')
    .analyze()

  expect(results.violations).toEqual([])
})

ใน suite จริงอาจ filter tags/rules ตาม standard scope ที่เจ้าของ accessibility กำหนด Exception ต้องมี rule, element, reason, owner และ review date ไม่ disable rule ทั่ว suite เพื่อให้ build เขียว Scan หน้า initial state ไม่ครอบ dialog/error/menu ที่ยังไม่เปิด ให้สร้าง tests ต่อ important state

ARIA Snapshot

await expect(page.getByRole('navigation', { name: 'Workshop filters' }))
  .toMatchAriaSnapshot(`
    - navigation "Workshop filters":
      - searchbox "Search workshops"
      - combobox "Level"
      - button "Apply filters"
  `)

ARIA snapshot ตรวจ accessibility tree structure เหมาะกับ landmark/menu/form ที่ stable ใช้ partial snapshot เล็ก แทน snapshot ทั้งหน้าเพื่อลด approval โดยไม่อ่าน Playwright 1.62.1 แก้ regression ของ ARIA snapshot จึงควร pin patch ล่าสุดเมื่อพึ่ง feature นี้

Visual Assertions

const card = page.getByRole('article').filter({ hasText: workshop.title })

await expect(card).toHaveScreenshot('full-workshop-card.webp', {
  animations: 'disabled',
  mask: [card.getByTestId('live-view-count')],
  maxDiffPixels: 20,
})

Playwright 1.62 รองรับ lossless WebP golden ผ่านนามสกุล .webp PNG ก็ใช้ได้ Baseline ต้องสร้างและรันบน OS, browser version, fonts, headless mode และ environment เดียวกัน Fix data, clock, locale, viewport และ animation ก่อน กำหนด threshold ค่า 20 เป็น illustrative ต้องวัด variance ของ component จริง ไม่เพิ่มจน defect หาย

ใช้ screenshot กับ region ที่ layout/visual state เป็น contract เช่น seat badge, overflow, focus ring หรือ responsive dialog ไม่ snapshot ทุกหน้า Baseline update ต้อง review expected/actual/diff และ commit พร้อมเหตุผล:

npx playwright test tests/ui/visual --update-snapshots

ห้ามให้ CI update snapshot อัตโนมัติ Mask เฉพาะ dynamic region ที่ไม่ใช่เรื่องของ test หาก mask ครึ่งหน้า visual assertion ไม่เหลือหลักฐาน

Device และ Environment Projects

projects: [
  {
    name: 'desktop-chromium',
    use: { ...devices['Desktop Chrome'] },
  },
  {
    name: 'mobile-webkit',
    use: { ...devices['iPhone 15'], colorScheme: 'light' },
  },
  {
    name: 'thai-dark-chromium',
    use: {
      ...devices['Desktop Chrome'],
      locale: 'th-TH',
      timezoneId: 'Asia/Bangkok',
      colorScheme: 'dark',
    },
  },
]

Device descriptor จำลอง user agent, screen/viewport และ touch Options จำลอง locale, timezone, geolocation, permissions, media และ color scheme ได้ แต่มันไม่ใช่เครื่องจริง จึงไม่พิสูจน์ virtual keyboard, browser shell, memory pressure, hardware หรือ OS integration ให้เพิ่ม real-device smoke เมื่อ risk เหล่านั้นสำคัญ

Matrix ไม่ควรเป็น desktop browsers × mobile profiles × locales × themes สำหรับทุก test เลือก scenario ที่ behavior ต่างจริง เช่น responsive navigation รัน desktop/mobile, localization layout รัน locale เป้าหมาย ส่วน API contract ไม่ต้อง multiply ตาม browser

Visual กับ Functional แยก Failure Intent

Functional assertion บอกว่า button มี/กดได้ Visual assertion บอก layout/pixels ตรง baseline Accessibility assertion บอก semantic rule/tree บางส่วน หากรวมทุกอย่างใน test เดียว failure triage ยาก แยก tags/suites ได้:

@smoke  critical functional journey
@a11y   semantic interaction and automated rules
@visual focused screenshot baselines
@mobile responsive behaviors

Tags เป็น course convention ให้สร้างเฉพาะเมื่อ CI/filter ใช้จริง

รายการตรวจสอบ

  • Keyboard/focus/state tests มาจาก explicit interaction contract
  • Axe scan ระบุ scope/state และไม่เคลม compliance ทั้งหมด
  • ARIA snapshots เล็กและ review ได้
  • Visual baseline ใช้ pinned, consistent environment
  • Dynamic data/time/animation ถูกควบคุมก่อนถ่าย
  • Emulation limits ถูกบอก และ matrix มาจาก support/risk evidence

สรุปบทนี้

Accessibility, pixels และ emulated environment เป็นหลักฐานคนละแกน ใช้ร่วมกันเพื่อเห็น defect มากขึ้น แต่ต้องรักษาขอบเขต: automated scan ไม่แทน human evaluation และ emulation ไม่แทน real device

อ่านเพิ่มเติม