웹 접근성 테스팅 가이드
“100% 통과했는데 왜 안 돼요?”
제품 출시 전날이었습니다. Lighthouse 접근성 점수 100점! axe DevTools도 위반사항 0개! 완벽했죠.
다음 날 아침, 실제 스크린 리더 사용자로부터 피드백이 왔습니다. “회원가입 폼을 찾을 수 없어요. 어디 있는 건가요?”
당황해서 NVDA로 직접 테스트해보니… 폼은 있었지만 “버튼, 버튼, 버튼, 버튼…“만 반복해서 들렸습니다. 각 버튼이 무엇인지 전혀 알 수 없었죠.
그때 깨달았습니다. 자동화 도구는 시작일 뿐, 진짜 테스트는 직접 해봐야 한다는 것을요.
접근성 테스팅의 진실
접근성 테스팅에 대한 불편한 진실:
자동화 도구가 찾는 문제: 30-40%
수동 테스트가 필요한 문제: 60-70%
왜 그럴까요?
자동화 도구는 “이미지에 alt가 있는가?”는 확인할 수 있지만, “alt 텍스트가 의미 있는가?”는 확인할 수 없습니다.
<!-- 자동화 도구: ✅ 통과 -->
<img src="photo.jpg" alt="image123">
<!-- 실제 사용자: ❌ 의미 없음 -->
스크린 리더: "이미지 123"
사용자: "이게 뭐지?"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- 올바른 alt -->
<img src="photo.jpg" alt="팀원들이 회의실에서 프로젝트를 논의하는 모습">
그래서 우리는 3가지 레이어의 테스팅이 필요합니다.
- 자동화 테스팅 - 빠른 기본 검사 (30-40%)
- 수동 테스팅 - 실제 사용성 확인 (60-70%)
- 사용자 테스팅 - 실제 장애인 사용자 피드백 (100%)
레이어 1: 자동화 테스팅 - 빠른 기본 검사
자동화 도구는 기본적인 문제를 빠르게 찾아냅니다. CI/CD에 통합하여 매번 자동 검사할 수 있습니다.
1. Lighthouse - Chrome에 내장된 감사 도구
왜 좋은가?
- Chrome에 이미 내장되어 있어 설치 불필요
- 성능, SEO, PWA 등 다른 지표도 함께 확인
- 개발 중 빠른 피드백
사용법:
# 방법 1: Chrome DevTools
1. F12 키
2. Lighthouse 탭
3. "Accessibility" 선택
4. "Analyze page load" 클릭
# 방법 2: CLI (CI/CD 통합)
npm install -g lighthouse
lighthouse https://yoursite.com --only-categories=accessibility --output html --output-path ./report.html
출력 예시:
Performance: 95
Accessibility: 78 👈 개선 필요
Best Practices: 92
SEO: 100
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Accessibility Issues:
❌ [button-name] Buttons do not have an accessible name
Found 3 elements
❌ [color-contrast] Background and foreground colors do not have sufficient contrast
Found 5 elements
⚠️ [image-alt] Image elements do not have [alt] attributes
Found 2 elements
장점:
- ✅ 즉시 사용 가능
- ✅ 종합 리포트
- ✅ CI/CD 통합 쉬움
단점:
- ❌ 페이지 로드 시점만 확인 (동적 콘텐츠 미확인)
- ❌ 기본적인 문제만 검사
2. axe DevTools - 가장 정확한 접근성 테스터
왜 좋은가?
- 업계 표준 (Deque Systems 개발)
- 가장 정확하고 false positive 적음
- WCAG 2.1 레벨별 검사
사용법:
# 방법 1: 브라우저 확장 프로그램
1. Chrome Web Store에서 "axe DevTools" 검색
2. 확장 프로그램 설치
3. F12 → axe DevTools 탭
4. "Scan ALL of my page" 클릭
# 방법 2: CLI
npm install --save-dev @axe-core/cli
npx axe https://yoursite.com --rules wcag2a,wcag2aa
# 방법 3: JavaScript API (테스트 통합)
npm install --save-dev axe-core
테스트 코드에서 사용:
import axe from 'axe-core';
// React Testing Library
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('should not have accessibility violations', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
출력 예시:
Violation 1: button-name
Impact: critical
Description: Buttons must have discernible text
WCAG: 4.1.2
Elements:
<button onclick="submit()">
<svg>...</svg> 👈 아이콘만 있음
</button>
How to fix:
- Add aria-label attribute
- Add text content
- Add aria-labelledby
장점:
- ✅ 가장 정확함
- ✅ 상세한 설명과 수정 방법 제공
- ✅ React, Vue, Angular 통합
- ✅ 동적 콘텐츠도 검사 가능
단점:
- ❌ 무료 버전은 기능 제한 (하지만 충분함)
3. WAVE - 시각적으로 문제 표시
왜 좋은가?
- 페이지에 직접 문제를 시각적으로 표시
- 초보자에게 가장 친화적
- 완전 무료
사용법:
# 방법 1: 브라우저 확장 프로그램
1. https://wave.webaim.org/extension/
2. 확장 프로그램 설치
3. 페이지에서 WAVE 아이콘 클릭
# 방법 2: 웹 인터페이스
https://wave.webaim.org/ 에서 URL 입력
장점:
- ✅ 문제 위치를 페이지에 직접 표시
- ✅ 무료
- ✅ 사용하기 쉬움
단점:
- ❌ 심층 분석 부족
- ❌ CI/CD 통합 어려움
4. pa11y - CI/CD에 최적화된 도구
왜 좋은가?
- CLI 친화적
- 여러 URL 일괄 테스트
- GitHub Actions, GitLab CI 등과 완벽 통합
사용법:
# 설치
npm install -g pa11y pa11y-ci
# 단일 URL 테스트
pa11y https://yoursite.com
# 여러 URL 테스트
pa11y-ci --config .pa11yrc.json
.pa11yrc.json 설정:
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 30000,
"wait": 1000,
"chromeLaunchConfig": {
"args": ["--no-sandbox"]
}
},
"urls": [
"https://yoursite.com",
"https://yoursite.com/about",
"https://yoursite.com/contact"
]
}
장점:
- ✅ CI/CD 통합 매우 쉬움
- ✅ 여러 페이지 자동 테스트
- ✅ 빌드 실패 조건 설정 가능
단점:
- ❌ 시각적 인터페이스 없음
5. eslint-plugin-jsx-a11y - 코딩 중 실시간 피드백
왜 좋은가?
- 문제를 조기에 발견 (코딩 중!)
- 팀 전체 일관성 유지
- 에디터에서 즉시 피드백
설치 및 설정:
npm install --save-dev eslint-plugin-jsx-a11y
.eslintrc.js:
module.exports = {
extends: [
'plugin:jsx-a11y/recommended'
],
plugins: ['jsx-a11y'],
rules: {
// 엄격 모드
'jsx-a11y/alt-text': 'error',
'jsx-a11y/anchor-has-content': 'error',
'jsx-a11y/aria-props': 'error',
'jsx-a11y/aria-role': 'error',
'jsx-a11y/click-events-have-key-events': 'error',
'jsx-a11y/label-has-associated-control': 'error',
'jsx-a11y/no-static-element-interactions': 'warn'
}
};
VS Code에서 실시간 피드백:
// ❌ 에러 표시
<img src="photo.jpg">
~~~~~~~~~~~~~~~~
Missing alt attribute
// ❌ 경고 표시
<div onClick={handleClick}>클릭</div>
~~~~~~~~~~~~~~~~~~~~~
onClick must be accompanied by onKeyDown/onKeyUp/onKeyPress
장점:
- ✅ 실시간 피드백
- ✅ 문제 조기 발견
- ✅ 자동 수정 (일부)
단점:
- ❌ React/JSX 전용
- ❌ 정적 분석의 한계
레이어 2: 수동 테스팅 - 실제 사용성 확인
자동화 도구는 30-40%만 찾습니다. 나머지 60-70%는 직접 해봐야 합니다.
1. 키보드 네비게이션 - 가장 중요한 테스트
시나리오:
여러분의 사이트를 마우스 없이 사용하는 사용자를 상상해보세요. 손이 불편해서, 또는 시각 장애가 있어서, 또는 단순히 마우스가 고장나서요.
테스트 방법:
1. 마우스를 물리적으로 치워두세요 (진짜로!)
2. Tab 키만으로 전체 사이트 탐색
3. 모든 기능을 키보드로 실행
4. 막히는 곳이 있으면 기록
체크리스트:
[ ] Tab으로 모든 인터랙티브 요소에 접근 가능
→ 버튼, 링크, 폼 필드, 드롭다운 등
[ ] Shift + Tab으로 역순 이동 가능
→ 이전 요소로 돌아갈 수 있어야 함
[ ] Enter로 버튼과 링크 활성화
→ <button>, <a> 요소
[ ] Space로 버튼, 체크박스 활성화
→ <button>, <input type="checkbox">
[ ] 화살표 키로 라디오 버튼, 탭, 메뉴 이동
→ 커스텀 위젯
[ ] Esc로 모달, 드롭다운 닫기
→ 닫기 기능 필수
[ ] 포커스 순서가 논리적
→ 위에서 아래, 왼쪽에서 오른쪽
[ ] 포커스 표시가 명확히 보임
→ 3px 이상 outline, 충분한 대비
[ ] 키보드 트랩 없음
→ 모달에서 빠져나올 수 있어야 함
[ ] 스킵 링크 작동
→ "본문으로 건너뛰기"
흔한 문제:
❌ div를 버튼으로 사용 (키보드 접근 불가)
<div onclick="submit()">제출</div>
✅ 진짜 button 사용
<button onclick="submit()">제출</button>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ 포커스 표시 제거
:focus { outline: none; }
✅ 명확한 포커스 표시
:focus-visible {
outline: 3px solid #ff230a;
outline-offset: 2px;
}
2. 스크린 리더 테스팅 - 가장 중요하지만 가장 어려운 테스트
왜 중요한가?
시각 장애인 사용자는 스크린 리더로 웹을 “듣습니다”. 자동화 도구는 스크린 리더 경험을 절대 확인할 수 없습니다.
VoiceOver (macOS) - 무료, 내장
활성화:
Command + F5
필수 단축키:
VO = Control + Option
VO + A : 웹 로터 (모든 요소 목록)
VO + → : 다음 요소
VO + ← : 이전 요소
VO + Command + H: 다음 헤딩
VO + Command + L: 다음 링크
VO + Space : 활성화 (클릭)
VO + U : 로터 열기
Control : 읽기 중지
실전 팁:
처음에는 혼란스러울 겁니다. 정상입니다!
1. 작은 페이지부터 시작 (로그인 폼 등)
2. 눈을 감고 들어보세요
3. "이게 뭔지 알겠어?"라고 자문
4. 헷갈리면 문제가 있는 것
NVDA (Windows) - 무료, 오픈소스
다운로드: https://www.nvaccess.org/download/
필수 단축키:
NVDA = Insert 또는 CapsLock
NVDA + Down : 다음 줄 읽기
NVDA + H : 다음 헤딩
NVDA + K : 다음 링크
NVDA + F : 다음 폼 필드
NVDA + T : 다음 테이블
Insert + F7 : 요소 목록
Control : 읽기 중지
스크린 리더 테스트 체크리스트
[ ] 페이지 제목이 적절히 읽힘
→ <title> 태그가 페이지 내용 설명
[ ] 헤딩 계층이 논리적
→ H1 → H2 → H3 순서대로
[ ] 이미지 alt 텍스트가 의미 전달
→ "image123" (X) / "팀원들이 회의하는 모습" (O)
[ ] 링크 텍스트가 목적을 설명
→ "여기 클릭" (X) / "가격표 다운로드" (O)
[ ] 폼 레이블이 명확
→ 각 input에 연결된 label
[ ] 에러 메시지가 읽힘
→ aria-describedby 또는 role="alert"
[ ] 동적 콘텐츠 변경이 알려짐
→ aria-live regions
[ ] 모달이 포커스 트랩
→ Tab 순환, Esc로 닫기
실제 테스트 시나리오:
시나리오: 로그인하기
1. 페이지 로드
→ "로그인 - 사이트명"
2. Tab으로 첫 입력 필드
→ "이메일, 텍스트 입력"
3. 이메일 입력
→ 입력한 글자 읽음
4. Tab으로 다음 필드
→ "비밀번호, 보안 텍스트 입력"
5. Tab으로 버튼
→ "로그인 버튼"
6. Enter로 제출
→ "로그인 중..." (aria-live)
→ "로그인 성공" 또는 "오류: ..."
3. 색상 대비 검사 - 읽기 쉬운가?
테스트 도구:
1. Chrome DevTools (가장 쉬움)
1. 요소 검사 (F12)
2. Elements 탭
3. Styles 패널에서 색상 값 찾기
4. 색상 박스 클릭
5. "Contrast ratio" 섹션 확인
→ AA, AAA 마크 확인
2. WebAIM Contrast Checker
https://webaim.org/resources/contrastchecker/
1. 전경색 (텍스트) 입력: #666666
2. 배경색 입력: #ffffff
3. 결과 확인:
Contrast Ratio: 5.74:1
✅ WCAG AA (4.5:1 이상)
❌ WCAG AAA (7:1 필요)
기준:
일반 텍스트 (18px 미만):
- WCAG AA: 4.5:1 ✅ 대부분 충족
- WCAG AAA: 7:1 ✅ 더 좋음
큰 텍스트 (18px 이상 또는 14px Bold):
- WCAG AA: 3:1
- WCAG AAA: 4.5:1
UI 컴포넌트 (버튼, 아이콘 등):
- WCAG AA: 3:1
자주 하는 실수:
❌ 대비 부족: 2.1:1
.text {
color: #cccccc;
background: #ffffff;
}
✅ 충분한 대비: 7.0:1
.text {
color: #595959;
background: #ffffff;
}
4. 확대/축소 테스팅 - 저시력 사용자
테스트:
브라우저 확대:
- 확대: Cmd/Ctrl + +
- 축소: Cmd/Ctrl + -
- 100%: Cmd/Ctrl + 0
체크리스트:
[ ] 200% 확대 시 레이아웃 깨지지 않음
→ 수평 스크롤 없음
[ ] 400% 확대 시 2차원 스크롤 불필요
→ 세로 스크롤만
[ ] 텍스트가 잘리지 않음
→ overflow: hidden 주의
[ ] 중요 기능에 접근 가능
→ 버튼이 화면 밖으로 안 나감
[ ] 고정 폰트 크기 사용 안 함
→ px 대신 rem, em 사용
좋은 예:
/* ✅ 상대 단위 사용 */
.text {
font-size: 1rem; /* 사용자 설정 반영 */
line-height: 1.5;
max-width: 70ch; /* 읽기 좋은 줄 길이 */
}
/* ❌ 고정 크기 */
.text {
font-size: 14px; /* 확대 안 됨 */
width: 600px; /* 반응형 아님 */
}
5. 모바일 접근성 - 터치와 제스처
체크리스트:
[ ] 터치 타겟 최소 44x44px
→ 손가락 크기
[ ] 터치 타겟 간 충분한 간격
→ 최소 8px
[ ] 핀치 줌 허용
→ viewport meta 태그 확인
[ ] 가로/세로 모드 모두 지원
→ orientation lock 금지
[ ] 모바일 스크린 리더 테스트
→ TalkBack (Android), VoiceOver (iOS)
나쁜 예:
<!-- ❌ 줌 차단 -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- ✅ 줌 허용 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
/* ❌ 터치 타겟 작음 */
.button {
width: 30px;
height: 30px;
}
/* ✅ 충분한 크기 */
.button {
min-width: 44px;
min-height: 44px;
padding: 12px 16px;
}
레이어 3: CI/CD 통합 - 자동으로 지키기
접근성을 한 번만 확인하는 것이 아니라, 매 배포마다 자동으로 확인하세요.
GitHub Actions 예시
name: Accessibility Tests
on: [push, pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Start server
run: npm start &
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run pa11y
run: |
npm install -g pa11y-ci
pa11y-ci --config .pa11yrc.json
- name: Run axe
run: |
npm install -g @axe-core/cli
axe http://localhost:3000 --exit
Jest + axe-core 통합
// setupTests.js
import { toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
// MyComponent.test.js
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
import MyComponent from './MyComponent';
test('should not have accessibility violations', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
빌드가 실패하면:
FAIL src/components/MyComponent.test.js
✕ should not have accessibility violations (123 ms)
Expected the HTML found at $('button') to have no violations:
<button onclick="submit()">
<svg>...</svg>
</button>
Received:
"Buttons must have discernible text (button-name)"
Fix any of the following:
- Element does not have inner text
- aria-label attribute does not exist
- aria-labelledby attribute does not exist
릴리스 전 체크리스트
🚨 필수 항목 (차단 이슈)
[ ] 자동화 도구로 critical 위반사항 0개
→ axe, Lighthouse 실행
[ ] 키보드만으로 모든 기능 사용 가능
→ 마우스 치우고 테스트
[ ] 포커스 표시 명확
→ 3px 이상 outline
[ ] 스크린 리더로 주요 플로우 테스트 완료
→ 회원가입, 로그인, 구매 등
[ ] 색상 대비 WCAG AA 이상
→ 4.5:1 (일반), 3:1 (큰 텍스트)
[ ] 모든 이미지에 의미 있는 alt
→ "image123" 같은 거 없음
[ ] 모든 폼 필드에 명확한 레이블
→ <label> 연결
[ ] 페이지 제목 적절
→ 각 페이지 고유 제목
[ ] 언어 속성 설정
→ <html lang="ko">
[ ] 에러 메시지 명확하고 도움이 됨
→ "오류" (X) / "비밀번호는 8자 이상" (O)
✅ 권장 항목 (개선 사항)
[ ] 200% 확대 시 레이아웃 유지
[ ] 스킵 링크 제공 ("본문으로 건너뛰기")
[ ] 랜드마크 역할 사용 (<main>, <nav> 등)
[ ] 헤딩 계층 논리적 (H1 → H2 → H3)
[ ] ARIA live regions 적절히 사용
[ ] 애니메이션 축소 모드 지원 (prefers-reduced-motion)
[ ] 터치 타겟 44x44px 이상
[ ] 고대비 모드 지원
문제 우선순위 - 무엇부터 고칠까?
🔴 Critical (즉시 수정)
사용자가 기능을 아예 사용할 수 없는 문제
- 키보드로 핵심 기능 접근 불가
- 스크린 리더로 주요 콘텐츠 인식 불가
- 폼 제출 불가능
- 색상 대비 심각하게 부족 (3:1 미만)
🟠 High (다음 배포 전 수정)
사용성을 크게 해치는 문제
- 포커스 표시 불명확
- 잘못된 ARIA 사용 (오히려 혼란)
- 에러 메시지 불명확
- alt 텍스트 누락 또는 의미 없음
🟡 Medium (개선 권장)
사용성을 해치지만 우회 가능한 문제
- 헤딩 순서 이상
- 랜드마크 누락
- 링크 텍스트 모호 (“여기 클릭”)
🟢 Low (시간 날 때)
마이너한 문제
- 미세한 색상 대비 이슈 (4.5:1 vs 7:1)
- 중복된 링크
- 긴 페이지에 스킵 링크 없음
유용한 북마클릿 (빠른 시각적 검사)
랜드마크 시각화
javascript:(function(){
const landmarks = document.querySelectorAll('[role], main, nav, header, footer, aside');
landmarks.forEach(el => {
el.style.outline = '3px solid red';
const role = el.getAttribute('role') || el.tagName.toLowerCase();
const label = document.createElement('div');
label.textContent = role;
label.style.cssText = 'position:absolute;background:red;color:white;padding:2px 5px;font-size:12px;z-index:10000;';
el.style.position = 'relative';
el.insertBefore(label, el.firstChild);
});
})();
헤딩 아웃라인
javascript:(function(){
const headings = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
headings.forEach(h => {
const level = h.tagName[1];
h.style.outline = `${level}px solid blue`;
h.style.outlineOffset = '2px';
});
})();
포커스 가능한 요소 하이라이트
javascript:(function(){
const focusable = document.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
focusable.forEach((el, i) => {
el.style.outline = '3px solid green';
el.title = `Tab order: ${i + 1}`;
});
})();
마치며
접근성 테스팅은 한 번의 이벤트가 아니라 지속적인 습관입니다.
저도 처음에는 “자동화 도구 100점이면 완벽하지!”라고 생각했습니다. 하지만 실제 사용자 피드백을 받고 나서야 깨달았죠. 자동화는 시작일 뿐, 진짜 테스트는 직접 해봐야 한다는 것을요.
오늘부터 시작하세요:
- 자동화 도구 실행 - Lighthouse 또는 axe (5분)
- 키보드만으로 탐색 - 마우스 치우기 (10분)
- 스크린 리더로 주요 플로우 테스트 - VoiceOver 또는 NVDA (15분)
매주 30분이면 충분합니다.
그리고 기억하세요:
“확인하지 않으면 알 수 없습니다.”
여러분의 사이트가 모두에게 접근 가능한지, 오늘 확인해보시겠어요? 🧪✨
참고 자료
자동화 도구
- axe DevTools - 가장 정확한 도구
- WAVE - 시각적 피드백
- Lighthouse - Chrome 내장
- pa11y - CI/CD 통합
스크린 리더
학습 자료
- WebAIM - 접근성 리소스
- The A11Y Project - 체크리스트와 팁
- Deque University - 심화 학습
관련 문서
함께 접근 가능한 웹을 만들어갑시다! 🚀
댓글