본문 바로가기
Frontend2025년 5월 30일9분 읽기

웹 접근성 자동 테스트 — axe-core와 Lighthouse 활용

YS
김영삼
조회 246

웹 접근성 테스트의 필요성

웹 접근성(a11y)은 장애를 가진 사용자도 웹 콘텐츠를 이용할 수 있도록 보장하는 것입니다. WCAG 2.1 지침을 준수해야 법적 요구사항을 충족하고, 더 많은 사용자에게 서비스를 제공할 수 있습니다. 자동 테스트로 접근성 문제의 약 30-50%를 탐지할 수 있으며, 나머지는 수동 테스트가 필요합니다.

axe-core 기본 사용

// axe-core 설치
npm install axe-core @axe-core/react @axe-core/playwright

// React 개발 환경에서 실시간 감지
// src/index.tsx
if (process.env.NODE_ENV === 'development') {
  import('@axe-core/react').then(axe => {
    axe.default(React, ReactDOM, 1000);
    // 콘솔에 접근성 위반 사항이 실시간 표시
  });
}

// 프로그래밍 방식으로 검사
import axe from 'axe-core';

async function runAccessibilityCheck() {
  const results = await axe.run(document, {
    rules: {
      'color-contrast': { enabled: true },
      'image-alt': { enabled: true },
    },
    tags: ['wcag2a', 'wcag2aa'],
  });

  console.log('위반:', results.violations.length);
  results.violations.forEach(v => {
    console.log(`[${v.impact}] ${v.id}: ${v.description}`);
    v.nodes.forEach(n => console.log('  -', n.html));
  });
}

Playwright + axe-core 통합 테스트

// tests/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('접근성 테스트', () => {
  test('홈페이지 접근성', async ({ page }) => {
    await page.goto('/');

    const results = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .exclude('.third-party-widget')  // 서드파티 제외
      .analyze();

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

  test('로그인 폼 접근성', async ({ page }) => {
    await page.goto('/login');

    const results = await new AxeBuilder({ page })
      .include('#login-form')  // 특정 영역만 검사
      .analyze();

    // 심각도별 필터링
    const critical = results.violations.filter(
      v => v.impact === 'critical' || v.impact === 'serious'
    );
    expect(critical).toEqual([]);
  });

  test('키보드 내비게이션', async ({ page }) => {
    await page.goto('/');

    // Tab 키로 모든 인터랙티브 요소 순회 가능한지 확인
    await page.keyboard.press('Tab');
    const firstFocused = await page.evaluate(() =>
      document.activeElement?.tagName
    );
    expect(firstFocused).toBeTruthy();

    // Skip to content 링크 확인
    const skipLink = page.locator('a[href="#main-content"]');
    await expect(skipLink).toBeFocused();
  });
});

Lighthouse CI 설정

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: [
        'http://localhost:3000/',
        'http://localhost:3000/about',
        'http://localhost:3000/login',
      ],
      numberOfRuns: 3,
      settings: {
        preset: 'desktop',
        onlyCategories: ['accessibility'],
      },
    },
    assert: {
      assertions: {
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'color-contrast': 'error',
        'image-alt': 'error',
        'link-name': 'error',
        'button-name': 'error',
        'html-has-lang': 'error',
        'meta-viewport': 'warn',
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

GitHub Actions CI 통합

# .github/workflows/a11y.yml
name: Accessibility Tests
on: [pull_request]

jobs:
  accessibility:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npm run build

      # Playwright + axe-core 테스트
      - run: npx playwright install --with-deps
      - run: npm run test:a11y

      # Lighthouse CI
      - run: npm install -g @lhci/cli
      - run: npm start &
      - run: lhci autorun

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: a11y-report
          path: .lighthouseci/

흔한 접근성 위반과 수정

위반심각도수정 방법
color-contrastserious텍스트/배경 대비 4.5:1 이상 보장
image-altcritical모든 img에 의미 있는 alt 속성
button-namecritical아이콘 버튼에 aria-label 추가
link-nameserious링크에 설명적 텍스트 제공
form-field-labelcritical모든 입력에 연결된 label
// 수정 예시
// BAD
<button><svg>...</svg></button>
<img src="logo.png">
<a href="/profile"><img src="avatar.png"></a>

// GOOD
<button aria-label="메뉴 열기"><svg aria-hidden="true">...</svg></button>
<img src="logo.png" alt="Young Sam 로고">
<a href="/profile" aria-label="내 프로필"><img src="avatar.png" alt=""></a>
  • 자동 테스트는 접근성 문제의 일부만 감지합니다 — 스크린 리더 테스트와 실제 사용자 테스트를 병행하세요
  • CI에서 critical/serious 위반은 빌드를 실패시키고, minor는 경고로 처리하는 것이 현실적입니다
  • 새로운 컴포넌트에 대한 접근성 테스트를 Storybook play function으로 작성하면 개발 단계에서 조기 발견할 수 있습니다

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록