본문으로 건너뛰기
5분 AI.
REPO DEEP DIVE · 2026-07-29 · DEQUELABS/AXE-CORE · 브라우저 안에서 도는 접근성 자동 검사 엔진

dequelabs/axe-core 딥다이브
— "규칙 하나하나가 selector + matches + checks"로 조립되는 방식

라이브러리

빠른 판단

무엇
웹페이지 DOM에 접근성 규칙을 적용해 위반과 수동 검토 대상을 분류하는 JavaScript 검사 엔진
이럴 때
브라우저 테스트에 접근성 검사를 추가하고 위반 요소·규칙을 CI 결과에서 확인할 때
입력
검사할 DOM 범위와 접근성 규칙 설정
결과
위반 요소·규칙별 결과와 추가 수동 검토 대상
axe-core는 웹페이지의 접근성(accessibility, 약칭 a11y) 문제를 브라우저 안에서 직접 실행해 찾아주는 테스트 엔진(라이브러리)이다. 사람이 눈으로 훑어보는 대신, "이미지에 대체 텍스트가 있는가", "글자와 배경의 색 대비가 충분한가", "버튼에 이름이 있는가" 같은 규칙 100여 개를 실제 DOM에 대고 돌려 violations(위반) · passes(통과) · incomplete(불확실) · inapplicable(해당없음) 4가지로 분류해 돌려준다.

Deque Systems가 만들고 오픈소스로 공개한 이 엔진은 그 자체로는 화면도, CLI도, 리포트 뷰어도 없다. 다른 도구들이 가져다 쓰는 "심장"이다 — 크롬 개발자도구의 Lighthouse 접근성 감사, axe DevTools 확장, Playwright/Selenium/Cypress용 axe 플러그인들이 전부 이 엔진 위에서 돌아간다.

(저장소 dequelabs/axe-core · 제작·유지보수 Deque Systems · 라이선스 MPL-2.0 · 버전 4.12.1(package.json 기준) · 런타임 의존성 0개(전부 빌드 시 axe.js 한 파일로 번들) · 규칙 파일 105개(lib/rules/*.json, 활성 100개) · 테스트 파일 654개 · 최신 커밋 9a779f1, 2026-07-27)
목차
  1. 한 줄 정체
  2. 왜 지금 뜨는가
  3. 무엇을 해결하나
  4. 전체 구조
  5. 핵심 설계·동작 원리
  6. 기술 스택·의존성
  7. 설치·요구사항
  8. 실제 사용 흐름
  9. 함정·오해 방지
  10. 대안 비교
  11. 정리 / 누가 쓰면 좋은가
  12. 직접 해볼 수 있는 실습 과제
  13. 관련 기술 심화 학습 로드맵
  14. 핵심 키워드 사전

1한 줄 정체

"웹페이지에 대고 접근성 규칙 100여 개를 돌려 4가지로 분류해주는 자바스크립트 엔진"

axe-core는 브라우저의 DOM을 직접 들여다보는 접근성 자동 검사기다. axe.run() 한 줄을 페이지 위에서 실행하면, 지금 화면에 그려진 요소들을 규칙(rule) 100여 개에 대고 하나씩 확인한다. 각 규칙은 "어떤 요소를 볼지(selector) → 그 요소가 이 규칙 대상이 맞는지(matches) → 실제 조건을 만족하는지(checks)"라는 3단 구조로 짜여 있고, 그 결과가 모여 violations(위반) · passes(통과) · incomplete(불확실, 사람이 봐야 함) · inapplicable(해당 요소 없음) 4개 배열로 반환된다.

중요한 건 axe-core 혼자서는 아무 화면도 보여주지 않는다는 점이다. npm으로 설치해 axe.js를 페이지에 넣고 콘솔이나 테스트 코드에서 axe.run()을 호출해야 결과(JSON)를 받는다. 그 JSON을 예쁘게 그려주는 일은 Lighthouse·axe DevTools·Playwright 리포터 같은 상위 도구들의 몫이다.

한 줄 비유

"건물 안전 점검관"이 체크리스트를 들고 층마다 돌며 표시를 남기는 일

axe-core는 체크리스트(규칙 100여 개)를 들고 건물(웹페이지)의 방(DOM 요소) 하나하나를 돈다. "이 문에 손잡이가 있는가", "이 계단에 손잡이가 있는가"처럼 정해진 항목만 확인하고, 각 항목을 합격 / 불합격 / "여기는 사람이 직접 봐야 함" / "이 방에는 해당 없음"으로 표시한다.

점검관은 보고서를 예쁘게 꾸며주지 않는다. 체크 결과 목록만 건네줄 뿐이고, 그걸 그래프로 그리거나 담당자에게 전달하는 건 다른 사람(다른 도구)의 일이다.

2왜 지금 주목받는가

웹 접근성 규제 강화 · "모든 접근성 도구의 뒷단"이라는 사실상 표준 지위 · AI 코드생성 시대의 안전망

axe-core는 2016년 첫 릴리스 이후 꾸준히 쓰여온 프로젝트라 "신생 트렌드"는 아니다. 그런데도 최근 다시 주목받는 이유는 아래 세 갈래로 정리된다.

① 접근성 규제가 전 세계적으로 강화되는 시기

유럽연합의 웹 접근성 지침(EN 301 549), 미국의 ADA(장애인차별금지법) 관련 소송 증가, 한국의 웹 접근성 품질인증 요구 등으로 기업들이 "우리 사이트가 WCAG를 지키는지"를 실제로 자동화된 코드로 증명해야 하는 상황이 늘었다. axe-core의 규칙 태그 체계에는 wcag2a·wcag21aa·section508·EN-301-549 같은 국제 표준 코드가 직접 박혀 있어, "이 코드가 어떤 법적 기준에 대응하는지"를 그대로 추적할 수 있다.

② 사실상 업계 표준 엔진 — 경쟁 도구조차 이걸 쓴다

구글 크롬 개발자도구에 내장된 Lighthouse의 접근성 감사가 실제로는 axe-core 기반 규칙을 가져다 쓰고, Deque 자체 상용 제품(axe DevTools)은 물론 @axe-core/playwright·@axe-core/react·webdriverjs·puppeteer용 공식 래퍼까지 전부 이 저장소 하나의 axe.core 엔진을 감싼 것이다. "접근성 자동 검사"라는 카테고리에서 이 저장소가 사실상 공용 코어 역할을 한다.

③ AI가 코드를 대량 생성하는 시대의 안전망

AI 코딩 도구가 UI 컴포넌트를 빠르게 찍어내는 지금, "빠르게 만든 코드가 스크린리더 사용자·저시력 사용자에게도 쓸 수 있는가"를 사람이 매번 손으로 확인하기는 현실적으로 어렵다. axe-core처럼 CI 파이프라인에 넣어 자동으로 회귀를 잡는 엔진의 필요성이 오히려 더 커졌다.

경쟁 지형
"직접 만든 검사기" 대신 "공용 엔진 위에 얹기"가 대세

Lighthouse·Pa11y·IBM Equal Access 같은 다른 접근성 도구들도 완전히 독자적인 검사 로직을 새로 짜기보다, WCAG 규칙 해석과 DOM 순회라는 어려운 부분은 axe-core 같은 검증된 엔진에 기대는 흐름이 강하다(대안 비교 절에서 상세히 다룬다). 그만큼 "규칙 엔진 자체"가 잘 만들어져 있으면 그 위 생태계 전체가 혜택을 본다.

3무엇을 해결하나

"접근성 검토는 사람이 수동으로, 느리게, 비일관되게 한다"는 문제를 자동화·표준화로 해결

접근성 문제(이미지에 설명 없음, 색 대비 부족, 폼 입력에 라벨 없음 등)는 전문 지식이 있어야 눈에 띄는 경우가 많고, 사람이 수동으로 훑으면 느리고, 사람마다 결과가 다르고, 매번 반복해야 하는 3중고가 있다. axe-core는 이 중 "기계적으로 판단 가능한 부분"을 명확히 잘라내 자동화한다.

해결 ① — 반복 검토를 코드 한 줄로

axe.run() 한 줄이면 화면에 보이는 모든 요소를 규칙 100여 개에 대해 검사한다. 사람이 페이지를 열 때마다 눈으로 훑을 필요 없이, 단위 테스트·통합 테스트 안에 끼워 넣어 매 커밋마다 자동으로 돌릴 수 있다. README의 "Getting started" 예시처럼 results.violations.length가 있으면 테스트를 실패시키는 식으로 CI에 바로 연결된다.

해결 ② — "확신 없는 결과"를 숨기지 않고 따로 분류

많은 접근성 문제는 기계가 100% 확신할 수 없다(예: 이미지의 대체 텍스트가 있긴 한데 그 내용이 적절한지는 사람이 봐야 안다). axe-core는 이런 경우를 억지로 통과·실패로 우기지 않고 incomplete(불확실)라는 별도 칸에 담아 "여기는 사람이 확인하라"고 명시적으로 신호를 준다. 이것이 4번째 결과 분류가 있는 근본 이유다(5번 섹션에서 소스로 자세히 짚는다).

해결 ③ — "이 규칙이 어떤 법적 기준인가"를 코드에 못박기

각 규칙 JSON에는 wcag2a·wcag21aa·best-practice·section508·EN-301-549 같은 태그가 붙어 있어, "이 위반이 WCAG 2.1 AA 기준 위반인지, 그냥 권장 사항(모범 사례)인지"를 코드 레벨에서 구분할 수 있다. 회사 정책이 "WCAG 2.1 AA까지만 강제한다"면 runOnly 옵션으로 그 태그만 골라 돌릴 수 있다.

용어
WCAG (Web Content Accessibility Guidelines)
W3C가 만든 웹 접근성 국제 표준 지침. "웹 콘텐츠 접근성 가이드라인"이라는 뜻이며, A·AA·AAA 세 단계 준수 수준이 있다. 대부분의 법·규제는 AA 수준을 요구한다. axe-core의 규칙 태그(wcag2a, wcag21aa 등)가 바로 이 기준 버전·레벨을 가리킨다.

4전체 구조

"규칙 정의(JSON) + 판정 함수(JS) → Audit(감사 세트) → Context(범위 결정) → 결과 집계" 파이프라인

axe-core의 소스는 lib/ 아래 core(실행 엔진), rules(규칙 정의), checks(판정 함수), commons(공용 로직 — 색·DOM·텍스트 등), standards(ARIA/HTML 명세 데이터) 다섯 축으로 나뉜다. 전체 흐름을 한 장으로 보면 이렇다.

┌─────────────────────────────────────────────────────────────────┐ │ AXE-CORE 실행 파이프라인 │ └─────────────────────────────────────────────────────────────────┘ [사용자 코드] axe.run(context?, options?) │ ▼ [1] lib/core/public/run.js setupGlobals → normalizeRunParams → assert(axe._audit) axe._running 락(lock)으로 동시 실행 방지 │ ▼ [2] lib/core/base/context.js 선택자(context)로 검사 "범위" 결정 + iframe들을 재귀 탐색 → select('frame, iframe', this) 로 프레임 큐에 등록 │ ▼ [3] lib/core/base/audit.js (Audit = 규칙 105개 + 체크 120개 묶음) 각 Rule에 대해 순서대로 실행 │ ▼ [4] lib/core/base/rule.js (규칙 하나) ├ gather() selector 로 후보 DOM 노드 수집 (VirtualNode 래핑) ├ matches() 이 규칙이 이 노드에 실제로 적용되는지 필터링 └ runChecks() any / all / none 체크 목록을 실행 │ ▼ [5] lib/core/base/check.js (체크 하나 — 진짜 판정 로직) evaluate 함수 실행 → true / false / undefined 반환 │ ▼ [6] lib/core/utils/aggregate-checks.js any/all/none 결과를 우선순위로 합쳐 노드 하나의 최종 판정 산출 │ ▼ [7] lib/core/utils/aggregate-result.js + constants.js NA(0)/PASS(1)/CANTTELL(2)/FAIL(3) 우선순위로 violations / passes / incomplete / inapplicable 4묶음 완성 │ ▼ [8] lib/core/reporters/v2.js 결과를 JSON으로 정리해 반환(Promise)

이 구조에서 가장 중요한 관찰은, "규칙(rule)"과 "체크(check)"가 분리돼 있다는 점이다. 규칙은 "어떤 요소를, 어떤 체크 조합으로 판정할지"를 정의한 선언(JSON)일 뿐이고, 실제 판정 코드(진짜 로직)는 체크 쪽에 있다. 체크는 여러 규칙에서 재사용된다 — 예를 들어 aria-label 체크는 이름을 요구하는 규칙 여러 개에서 공유된다.

5핵심 설계·동작 원리

규칙 = selector + matches + checks(any/all/none) · 결과 4분류의 우선순위 산술 · 색 대비 계산 · VirtualNode와 프레임 순회

이 섹션이 이 문서에서 가장 두꺼운 이유는, axe-core를 "그냥 쓰는" 사람과 "왜 이렇게 결과가 나오는지 이해하는" 사람의 차이가 전부 여기 있기 때문이다. 실제 소스 파일과 함수명을 하나씩 짚는다.

① 규칙 하나의 실체 — lib/rules/color-contrast.json

가장 유명한 규칙 하나를 실제 파일 그대로 보자(주석은 설명용으로 추가).

{
  "id": "color-contrast",              // 규칙 고유 ID
  "impact": "serious",                 // 위반 시 심각도
  "matches": "color-contrast-matches", // 이 규칙이 적용될 노드를 거르는 함수
  "excludeHidden": false,
  "tags": [
    "cat.color", "wcag2aa", "wcag143", // WCAG 2 AA / 기준 1.4.3
    "TTv5", "EN-301-549", "ACT"
  ],
  "actIds": ["afw4f7", "09o5cg"],       // W3C ACT 규칙 대응 ID
  "metadata": {
    "description": "...",
    "help": "Elements must meet minimum color contrast ratio thresholds"
  },
  "all": [],
  "any": ["color-contrast"],           // any 목록에 체크 1개
  "none": []
}

selector 필드가 없으면 lib/core/base/rule.js의 Rule 생성자가 this.selector = spec.selector || '*'로 기본값을 채운다. 즉 규칙 JSON은 "어떤 요소(selector) + 어떤 필터(matches) + 어떤 체크 묶음(any/all/none)"이라는 선언일 뿐이고, 실제 실행은 자바스크립트 코드(Rule.prototype)가 담당한다.

용어
any / all / none
규칙 하나가 여러 체크(check)를 어떻게 조합할지 정하는 3종 목록. any는 "이 중 하나라도 통과하면 됨"(OR), all은 "전부 통과해야 함"(AND), none은 "이 중 하나라도 걸리면 실패"(부정 조건)다. 하나의 규칙이 이 셋을 섞어 복잡한 조건도 표현할 수 있다.

② 규칙 실행 — gather → matches → runChecks

lib/core/base/rule.js의 Rule.prototype.gatherAndMatchNodes가 실제 흐름을 보여준다.

Rule.prototype.gatherAndMatchNodes = function (context, options) {
  let nodes = this.gather(context, options);      // selector 로 후보 수집
  return nodes.filter(node => {
    return this.matches(node.actualNode, node, context); // matches 함수로 필터
  });
};

그 뒤 Rule.prototype.run은 살아남은 노드마다 ['any', 'all', 'none'].forEach(type => ...)로 세 목록을 각각 runChecks에 넘겨 병렬 실행하고, 노드별로 { any: [...], all: [...], none: [...] } 형태의 원시 결과를 만든다. runChecks 내부에서는 각 체크의 check.run(node, option, context, resolve, reject)를 호출하는데, 이 check.run이 바로 다음 항목이다.

③ 체크 하나의 실체 — evaluate 함수와 true/false/undefined

lib/core/base/check.js의 체크 실행은 checkHelper가 만든 this(data()·relatedNodes()·async() 메서드 제공) 컨텍스트 안에서 evaluate 함수를 돌린다. evaluate가 true를 반환하면 통과, false면 실패, undefined를 반환하면 "확신 못 함"으로 취급된다 — 이 세 번째 값이 바로 incomplete가 생기는 근원지다.

④ any/all/none을 하나의 판정으로 합치기 — 우선순위 산술

세 목록의 개별 결과를 노드 하나의 최종 판정으로 합치는 코드가 lib/core/utils/aggregate-checks.js다. 여기서 핵심은 결과값을 우선순위 숫자로 바꿔 최댓값을 취하는 산술이다.

const checkMap = [];
checkMap[PASS_PRIO] = true;      // 우선순위 1
checkMap[CANTTELL_PRIO] = null;  // 우선순위 2
checkMap[FAIL_PRIO] = false;     // 우선순위 3

// none 타입은 pass/fail 을 뒤집는다 (있으면 실패인 규칙이므로)
if (type === 'none') {
  if (check.priority === PASS_PRIO) check.priority = FAIL_PRIO;
  else if (check.priority === FAIL_PRIO) check.priority = PASS_PRIO;
}

priorities.all  = max(all 체크들의 priority, 0 초기값)
priorities.none = max(none 체크들의 priority, 0 초기값)
priorities.any  = min(any 체크들의 priority, 4) % 4   // any는 "가장 낮은(가장 잘 통과한)" 것 채택

nodeResult.priority = max(all, none, any)  // 셋 중 가장 나쁜(높은) 것이 최종 판정

any는 "하나만 통과해도 됨"이므로 가장 낮은 우선순위(가장 좋은 결과)를 찾고, all과 none은 "하나라도 걸리면 안 됨"이므로 가장 높은 우선순위(가장 나쁜 결과)를 찾는다. 그리고 세 그룹 중에서도 다시 최댓값을 취해 "하나라도 나쁘면 전체가 나쁘다"는 논리를 관철한다. 이 코드 한 파일이 "규칙 하나 = selector + matches + checks 조합"이라는 아키텍처의 실제 심장이다.

⑤ 4가지 결과 분류의 실제 정의 — lib/core/constants.js

violations/passes/incomplete/inapplicable이라는 이름이 어디서 나오는지, 소스에 그대로 있다.

const definitions = [
  { name: 'NA',       value: 'inapplicable', priority: 0, group: 'inapplicable' },
  { name: 'PASS',     value: 'passed',       priority: 1, group: 'passes' },
  { name: 'CANTTELL', value: 'cantTell',     priority: 2, group: 'incomplete' },
  { name: 'FAIL',     value: 'failed',       priority: 3, group: 'violations' }
];

lib/core/utils/aggregate-result.js는 각 노드 결과를 이 우선순위에 따라 4개 배열 중 하나로 나눈다. 에러가 났으면 강제로 incomplete, 결과가 NA면 inapplicable, 그 외엔 해당 그룹 배열에 내용이 있는지로 판단한다.

비유

공항 보안 검색대의 4가지 도장을 떠올리면 쉽다. "통과"(passes), "반입 금지 확정"(violations), "다시 열어봐야 함 — 담당자가 눈으로 확인"(incomplete), "이 검색대에서 다룰 물건이 아님, 예를 들어 액체가 아닌 가방"(inapplicable). 검색대는 애매하면 억지로 통과·금지를 결정짓지 않고 "확인 필요" 도장을 따로 찍는다 — 이게 incomplete가 왜 중요한지의 본질이다.

왜 중요한가
incomplete를 무시하면 "거짓 안전감"에 빠진다

많은 팀이 results.violations.length === 0만 확인하고 results.incomplete는 아예 안 본다. 하지만 axe-core README가 명시하듯 "자동으로 찾을 수 있는 WCAG 문제는 평균 57%뿐"이며, incomplete는 "기계가 확신할 수 없어 일부러 판정을 유보한" 항목이다. 즉 violations가 0이어도 접근성이 완벽하단 뜻이 아니다 — incomplete를 사람이 검토해야 진짜 커버리지가 올라간다.

⑥ 색 대비 계산 — 실제 공식이 그대로 코드에 있다

color-contrast 체크의 핵심 수학은 lib/commons/color/get-contrast.js 한 파일에 있다.

function getContrast(bgColor, fgColor) {
  if (!fgColor || !bgColor) return null;
  if (fgColor.alpha < 1) {
    fgColor = flattenColors(fgColor, bgColor);   // 반투명이면 배경과 합성
  }
  const bL = bgColor.getRelativeLuminance();      // 상대 휘도(밝기)
  const fL = fgColor.getRelativeLuminance();
  return (Math.max(fL, bL) + 0.05) / (Math.min(fL, bL) + 0.05); // WCAG 공식
}

이건 WCAG 2 명세에 나오는 상대 휘도 대비 공식 그 자체다. 이렇게 구한 비율은 has-valid-contrast-ratio.js에서 글자 크기·굵기에 따라 4.5:1(작은 글자) 또는 3:1(큰 글자·굵은 글자) 기준과 비교된다. 실제로는 이 앞에 get-foreground-color.js·get-background-color.js·flatten-colors.js·stacking-context.js 같은 파일들이 "이 텍스트의 진짜 전경색·배경색이 뭔지"(그림자, 겹친 레이어, 투명도까지 고려)를 먼저 알아내는 훨씬 복잡한 선행 작업을 한다 — 색상 연산 자체는 devDependency인 colorjs.io 라이브러리가 담당한다.

함정
jsdom(Node.js 가짜 브라우저)에서는 color-contrast가 안 된다

README가 직접 명시한다 — "jsdom에 대한 지원은 제한적이며, 특히 color-contrast 규칙은 jsdom에서 동작하지 않는 것으로 알려져 있다." 실제 컴퓨티드 스타일·레이어링 렌더링이 필요한 체크라 진짜 브라우저 렌더 엔진이 있어야 한다. Node 스크립트에서 axe-core를 돌릴 때 이 규칙만 유독 안 잡히면 당황하지 말 것 — 설계상 한계다. (실제로 최신 커밋도 "jsdom 30이 Node 20 지원을 끊어서 테스트 매트릭스를 손봤다"는 내용이라, 이 jsdom 경계는 지금도 계속 관리 중인 영역이다.)

⑦ VirtualNode — 진짜 DOM과 판정 로직 사이의 완충지대

axe-core는 규칙·체크가 진짜 브라우저 Node 객체를 직접 쥐지 않고, lib/core/base/virtual-node/의 AbstractVirtualNode를 상속한 VirtualNode(진짜 DOM용)·SerialVirtualNode(직렬화된 가짜 DOM용, 다른 프레임에서 넘어온 데이터를 재구성할 때 사용)로 감싸 쓴다. hasClass() 같은 공통 헬퍼를 여기 한 곳에 두고, "노드가 진짜 DOM인지 프레임 너머에서 온 데이터인지"를 규칙 코드가 신경 쓰지 않아도 되게 만든다.

⑧ 프레임(iframe) 순회 — 무한 깊이까지 재귀

lib/core/base/context.js는 select('frame, iframe', this)로 현재 범위 안의 모든 프레임을 찾고, pushUniqueFrame()이 화면에 보이지 않는(스크린리더에 노출 안 되는) iframe은 건너뛰며 나머지를 context.frames 큐에 쌓는다. 각 프레임은 createFrameContext()로 별도 Context를 만들어 재귀적으로 같은 파이프라인을 탄다. README의 매니페스토에 있는 "무한 깊이의 iframe을 지원한다"는 문장이 정확히 이 코드로 구현돼 있다.

같은 창(window) 안의 프레임은 lib/core/utils/respondable.js가 postMessage 기반으로 부모↔자식 프레임 간에 결과를 주고받는다. 반면 Playwright·Selenium처럼 브라우저를 외부에서 원격 조종하는 자동화 도구는 postMessage 왕복이 번거로우므로, lib/core/public/run-partial.js + finish-run.js라는 별도 경로를 쓴다 — 각 프레임에서 axe.runPartial()을 개별 실행해 부분 결과를 얻고, 최상위에서 axe.finishRun()으로 그 부분 결과들을 한 번에 병합한다. "같은 결과를 얻는 두 가지 경로"가 실제로 코드에 나뉘어 있다는 것 자체가 이 엔진이 다양한 실행 환경(브라우저 내부 vs 자동화 드라이버)을 어떻게 다르게 다루는지 보여주는 좋은 예다.

용어
Context
이번 검사가 어디까지를 범위로 하는지 정의하는 객체(include/exclude 선택자, 프레임 목록 등). axe.run()에 아무것도 안 주면 문서 전체가 기본 Context가 된다.

6기술 스택·의존성

런타임 의존성 0개(전부 번들) · esbuild 빌드(Grunt 아님) · colorjs.io·css-selector-parser·doT 내장

package.json을 열어보면 놀라운 점이 하나 있다 — dependencies 필드가 아예 없다. npm으로 axe-core를 설치해도 딸려오는 런타임 패키지가 0개다. 대신 필요한 라이브러리는 전부 빌드 시점에 최종 산출물(axe.js/axe.min.js) 안으로 번들된다.

번들 내장 라이브러리역할
colorjs.io색 대비 계산의 색 공간 연산(휘도, RGB 변환)
css-selector-parser사용자가 준 CSS 선택자를 파싱
@deque/dotdoT 템플릿 엔진 — 체크 실패 메시지에 변수 끼워넣기
emoji-regex텍스트 안의 이모지를 인식(대체 텍스트 판정 등에 사용)
memoizee같은 계산을 반복하지 않도록 메모이제이션(결과 재사용)
core-js-pure구형 브라우저(IE11 등)를 위한 Array.from 등 폴리필

이 목록은 lib/core/imports/index.js에 그대로 나열돼 있고, 전부 package.json에는 devDependencies로만 등록돼 있다. 즉 "버전의 진실원"은 이 저장소의 package.json의 version 필드(현재 4.12.1) 하나뿐이며, 이 값이 빌드 스크립트를 통해 axe.js 파일 맨 위 주석(/*! axe v4.12.1 ...*/)과 각 규칙 도움말 URL(dequeuniversity.com/rules/axe/4.12/...)에 그대로 찍힌다.

빌드 도구 — 문서와 실제가 어긋나는 지점

함정 — 문서/블로그와 실제 코드의 불일치
"Grunt로 빌드한다"는 오래된 정보는 이제 틀렸다

인터넷에 떠도는 오래된 자료·튜토리얼은 axe-core가 Grunt로 빌드된다고 설명하는 경우가 많다(실제로 예전 버전은 Gruntfile.js를 썼다). 하지만 지금 소스에는 Gruntfile이 없고, build/run-build.mjs 파일 맨 위 주석에 이렇게 적혀 있다: /** axe-core build driver (replaces Gruntfile.js). */. 즉 Grunt는 esbuild 기반의 자체 빌드 스크립트로 완전히 대체됐다. "Grunt task를 어떻게 커스터마이징하나"를 찾으러 왔다면 이 저장소에서는 헛수고다 — build/run-build/ 아래의 커맨드 파서·풀빌드·번역·검증 로직을 봐야 한다.

빌드 산출물 — IIFE 하나로 포장

최종 axe.js는 lib/intro.stub(라이선스 헤더 + IIFE 시작)과 lib/outro.stub(IIFE 종료)으로 모든 소스를 감싼 즉시실행함수(IIFE) 한 덩어리다. <script src="axe.js"> 태그 하나만 넣으면 전역에 window.axe가 생기는 이유가 여기 있다 — 모듈 로더가 없던 시절부터 이어진, 어떤 환경에서도 그냥 붙여넣으면 도는 설계다.

기술 스택 요약표

영역내용
언어순수 JavaScript(ESM 소스, esbuild로 번들) + 타입 정의 axe.d.ts(TypeScript 사용자용)
빌드esbuild 기반 build/run-build.mjs(Grunt 대체) — build/configure/translate/validate 서브커맨드
테스트@web/test-runner(단위) · Mocha + Selenium/Chromedriver(통합) · jsdom(Node 환경 일부 테스트)
다국어locales/*.json 20개 언어(한국어 ko.json 포함) — 규칙 메시지만 번역, 엔진 자체는 언어 무관
배포npm 패키지 axe-core(files: axe.js, axe.min.js, axe.d.ts, locales/) · release.yml이 commit-and-tag-version으로 버전 태깅
용어
IIFE (Immediately Invoked Function Expression)
"즉시 실행 함수 표현식" — 정의하자마자 바로 실행되는 함수. 전역 변수 오염을 최소화하면서도 모듈 시스템 없이 스크립트 태그 하나로 전역 객체(여기선 window.axe)를 노출하는 오래된 표준 패턴이다.

7설치·요구사항

npm install 한 줄 · 지원 브라우저 명시 · Node/jsdom 제한 사항 숙지
항목요구사항
설치npm install axe-core --save-dev — 런타임 의존성 0개라 설치가 매우 빠르고 가볍다
페이지에 삽입<script src="node_modules/axe-core/axe.min.js"></script> — 검사하려는 모든 iframe 안에도 넣어야 한다
실행axe.run().then(results => ...) — Promise 기반, 콜백 방식도 지원
Node.js 개발 환경engines.node는 >=4로 넓게 열려 있지만, 실제 개발·테스트는 최신 LTS + jsdom 조합을 요구(최근 커밋에서 jsdom 30이 Node 20 지원을 끊어 테스트 매트릭스를 조정)
지원 브라우저Edge 40+, Chrome 42+, Firefox 38+, Safari 7+. IE11은 지원 종료(DEPRECATED) 명시. CI에서는 Chrome·Firefox만 매 PR마다 자동 테스트
jsdom(Node 안의 가짜 브라우저)제한적 지원 — color-contrast 규칙은 공식적으로 동작 보장 안 됨
가볍게 시작하는 팁
직접 스크립트 태그로 붙이지 말고, 통합 라이브러리로 시작하라

실무에서는 axe-core를 <script> 태그로 직접 넣는 경우보다, @axe-core/playwright나 @axe-core/webdriverio 같은 공식 래퍼로 시작하는 편이 훨씬 편하다. 이 저장소는 "엔진"만 제공하고, 저 래퍼들이 "브라우저에 스크립트 주입 + 실행 + 결과 리포트"까지 대신 해준다.

8실제 사용 흐름

CI 테스트에 끼워 넣기 → violations 확인 → incomplete 수동 검토 → 옵션으로 범위/기준 조정

가장 흔한 실전 흐름은 "기능 테스트를 짜는 김에 접근성 검사도 같이 돌리는" 방식이다. 아래는 README에 있는 최소 예시를 뼈대로 한 사용 순서다.

1단계 — 검사 실행

axe
  .run()
  .then(results => {
    if (results.violations.length) {
      throw new Error('접근성 문제 발견');    // 위반이 있으면 테스트 실패 처리
    }
  })
  .catch(err => {
    console.error('실행 중 오류:', err.message);
  });

초보자를 위한 한 줄 풀이: "지금 화면에 대고 검사를 돌려서(run), 다 끝나면(then) 위반 목록(violations)이 하나라도 있으면 에러를 던지고, 검사 자체가 실패하면(catch) 그 이유를 콘솔에 찍어라"는 뜻이다. 복사해서 테스트 파일 안에 그대로 붙여도 동작한다.

2단계 — 결과 4묶음을 각각 다르게 다루기

const { violations, incomplete, passes, inapplicable } = results;
console.log('위반(반드시 고쳐야 함):', violations.length);
console.log('불확실(사람이 확인):', incomplete.length);   // 여기를 무시하지 말 것
console.log('통과:', passes.length);
console.log('해당없음:', inapplicable.length);

3단계 — 범위·기준을 옵션으로 좁히기

axe.run(document, {
  runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] }, // WCAG 2 A/AA만
  exclude: [['#legacy-widget']]                            // 낡은 위젯은 검사 제외
});

runOnly에 태그를 넘기면, 그 태그가 붙은 규칙 JSON만 골라 Audit이 구성된다(4번 섹션의 태그 체계가 바로 이렇게 실전에서 쓰인다). 회사 정책이 "베스트 프랙티스는 참고만, 법적 기준(WCAG 2 A/AA)만 강제"라면 이렇게 좁혀 CI를 더 엄격하지 않게 통제할 수 있다.

4단계 — 자동화 프레임워크에서는 래퍼를 통해

Playwright라면 @axe-core/playwright의 AxeBuilder가 "페이지에 axe.js 주입 → runPartial/finishRun으로 프레임까지 처리 → 결과 반환"을 대신 해준다. 이 저장소 자체를 직접 만질 일은, 웹앱을 만드는 대부분의 팀에게는 거의 없다 — 대신 저 래퍼들의 옵션(포함/제외 선택자, 태그 필터)을 다루게 된다.

9함정·오해 방지

"자동 검사 = 완전한 검사"라는 착각, 동시 실행 락, 폐지된 규칙, 색 대비의 숨은 전제

오해 ① — "violations가 0이면 우리 사이트는 접근성이 완벽하다"

README가 직접 못박은 숫자가 있다: "axe-core로 평균 WCAG 이슈의 57%를 자동으로 찾을 수 있다." 나머지 43%는 애초에 기계가 판단할 수 없는 영역(문맥적 의미, 실제 사용성, 논리적 읽기 순서의 자연스러움 등)이다. axe-core는 스스로도 "제로 거짓양성(zero false positives)"을 목표로 내세우지만, 동시에 "발견하지 못하는 진짜 문제(false negative)"가 훨씬 많다는 걸 인정하는 셈이다. 자동 검사 통과는 "최소한의 바닥은 다졌다"는 뜻이지 "접근성 인증"이 아니다.

오해 ② — axe.run()을 동시에 두 번 부를 수 있다

lib/core/public/run.js는 axe._running 플래그로 동시 실행을 원천 차단한다. 이미 실행 중일 때 다시 axe.run()을 부르면 "Axe is already running..." 에러가 던져진다. 비동기 테스트 코드에서 await를 빼먹고 두 검사를 동시에 걸면 이 에러를 마주친다 — axe-core 버그가 아니라 의도된 안전장치다.

오해 ③ — 105개 규칙이 전부 항상 실행된다

lib/rules/ 안의 JSON 파일은 105개이지만, 이 중 duplicate-id·duplicate-id-active·audio-caption·aria-roledescription·landmark-complementary-is-top-level 5개는 파일 안에 "enabled": false가 박혀 있는 폐지(deprecated) 규칙이다. 기본 실행에서는 100개만 돈다. 오래된 예제 코드에서 저 규칙 이름을 보고 "지금도 기본으로 도는 규칙이겠지" 오해하면 안 된다 — 하위 호환을 위해 코드만 남겨둔 것이다.

오해 ④ — 색 대비 규칙은 "색상 값"만 보면 계산된다

실제로는 get-foreground-color·get-background-color가 텍스트 그림자, 겹친 레이어(stacking context), 반투명도까지 역추적해야 색이 확정된다. 이 과정에서 값을 확정 지을 수 없으면(예: 배경이 이미지라 실제 픽셀 색을 알 수 없을 때) 이 체크도 incomplete로 떨어진다 — "색 대비 위반이 안 뜨니 통과"가 아니라 "색을 특정 못 해 사람이 봐야 하는" 경우가 섞여 있을 수 있다.

주의
이 저장소를 고쳐도 당신 사이트의 axe 결과는 안 바뀐다

많은 개발자가 규칙 메시지나 판정 로직을 커스터마이징하고 싶어 dequelabs/axe-core 저장소를 포크해 수정한다. 하지만 실제 서비스에 쓰이는 건 npm에 배포된 빌드된 axe.js이므로, 소스를 고쳤다면 반드시 npm run build로 다시 번들링해 그 결과물을 배포 파이프라인에 반영해야 한다. 소스만 고치고 그대로 두면 아무 변화가 없다.

10대안 비교

Lighthouse a11y · Pa11y · IBM Equal Access · WAVE — "완전히 다른 엔진"이 아니라 "같은 엔진의 다른 포장"인 경우가 많다
도구axe-core와의 관계 · 특징
Lighthouse
(a11y 카테고리)
구글 크롬 개발자도구 내장 감사 도구. 접근성 카테고리의 규칙 다수가 실제로 axe-core를 내부에서 가져다 쓴다(doc/projects.md에 axe-core를 쓰는 커뮤니티 프로젝트로 Lighthouse가 직접 명시돼 있다). 성능·SEO 등 다른 카테고리까지 한 번에 보고 싶을 때 유리하지만, 접근성만 놓고 보면 axe-core의 부분집합에 가깝다.
Pa11y독자적인 CLI/Node 도구로, 내부적으로 axe-core 또는 HTML_CodeSniffer 엔진을 선택해 쓸 수 있는 구조. "명령줄에서 URL 하나 던지면 리포트가 나온다"는 접근성에 강해, CI에 붙이기 쉬운 경량 래퍼로 자주 쓰인다.
IBM Equal Access
(accessibility-checker)
IBM이 만든 독자 엔진 + 브라우저 확장. axe-core와 별개의 규칙셋을 직접 구현했고, IBM 자체 접근성 요구사항(트러스티드 접근성 체크리스트)에 특화된 규칙이 강점이다. axe-core보다 규칙 수는 적지만 대기업 내부 정책과의 정합성을 노린 도구.
WAVE
(WebAIM)
브라우저 확장/웹 서비스 형태로, 결과를 페이지 위에 아이콘으로 시각적으로 오버레이해 보여주는 게 특징. 자동 판정 엔진은 자체 개발이며, "코드가 아니라 화면으로 바로 이해하고 싶은" 디자이너·기획자에게 특히 친절하다. CI 자동화보다는 수동 점검·교육용으로 강하다.
선택 기준
"CI에 박아 자동화"할 거면 axe-core(또는 그 래퍼), "눈으로 훑어보며 배울" 거면 WAVE

axe-core는 테스트 코드·CI 파이프라인에 통합하는 용도로 가장 강하다(같은 검사를 매 배포마다 반복하는 데 최적화). Lighthouse는 "성능까지 한 번에 종합 점검"하고 싶을 때, Pa11y는 "가벼운 CLI로 URL만 검사"하고 싶을 때, IBM Equal Access는 "회사 내부 접근성 정책과 정확히 맞춰야" 할 때, WAVE는 "처음 접근성을 배우며 화면으로 직관적으로 확인"하고 싶을 때 적합하다. 이 넷은 서로 배타적이지 않다 — 실무에서는 CI에는 axe-core 계열, 수동 리뷰에는 WAVE처럼 병행하는 경우가 흔하다.

11정리 / 누가 쓰면 좋은가

"접근성 회귀를 코드로 자동 감시하고 싶은" 모든 프론트엔드 팀에게

axe-core는 "규칙(selector+matches+checks) → 우선순위 산술로 4분류 → 상위 도구가 소비"라는 단순하지만 정교한 파이프라인 하나로, 웹 접근성 자동 검사라는 분야의 사실상 표준 엔진이 됐다. 런타임 의존성 0개로 가볍게 배포되고, 105개 규칙·20개 언어·무한 깊이 iframe 지원까지 갖춘 성숙한 프로젝트다.

이 저장소를 직접 들여다볼 가치가 있는 사람은 다음과 같다.

반대로, 단순히 "우리 사이트 접근성 검사만 한 번 돌려보고 싶다"면 이 저장소 소스를 직접 읽을 필요 없이 @axe-core/playwright 같은 통합 라이브러리나 axe DevTools 브라우저 확장으로 충분하다. 이 문서는 "그 도구들 안에서 실제로 무슨 일이 벌어지는지" 궁금한 사람을 위한 것이다.

12직접 해볼 수 있는 실습 과제

작은 실패를 재현하고, 검사 범위를 바꾼 뒤, 판정 로직을 테스트로 설명하기

실습은 의존성이 준비된 별도의 학습용 작업 사본에서 진행한다. 명령은 저장소 루트 기준이며, Node.js는 .nvmrc, pnpm은 package.json의 packageManager를 확인해 맞춘다. 먼저 아래 명령으로 로컬 소스를 axe.js로 빌드한다.

pnpm run build

앞의 설치 예시는 웹앱에서 패키지를 사용하는 방법이고, 여기서는 엔진 소스를 직접 빌드한다. 과제 1·2는 doc/examples/jsdom/test/a11y.js의 실행 방식을 응용하며, 과제 3·4의 브라우저 테스트에는 Chrome과 개발 의존성의 chromedriver도 준비되어 있어야 한다.

과제 1

이름 없는 버튼을 고쳐 결과 비교하기 난이도 · 입문

목표: 같은 요소의 내용을 바꾸면 위반이 통과로 이동하는지 확인한다. 화면 전체의 문제와 섞이지 않도록 button-name 규칙만 실행한다.

  1. doc/examples/jsdom/test/a11y.js에서 DOM 요소를 axe.run()의 첫 인자로 전달하는 부분을 읽는다.
  2. 아래 블록을 저장소 루트의 터미널에 그대로 복사해 실행한다. 배포 패키지 대신 방금 빌드한 ./axe.js를 불러온다.
  3. 각 결과 묶음의 수정 전·후 개수를 비교하고, 버튼의 글자를 다시 빈 문자열로 바꾸면 위반이 재현되는지 확인한다.
node <<'NODE'
const { JSDOM } = require('jsdom');
const axe = require('./axe.js');

(async () => {
  const { document } = new JSDOM('<main><button></button></main>').window;
  const root = document.querySelector('main');
  const options = { runOnly: { type: 'rule', values: ['button-name'] } };
  const before = await axe.run(root, options);

  document.querySelector('button').textContent = '저장';
  const after = await axe.run(root, options);

  for (const key of ['violations', 'passes', 'incomplete', 'inapplicable']) {
    console.log(key, before[key].length, '→', after[key].length);
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});
NODE

코드 풀이: JSDOM이 메모리 안에 버튼을 만들고, 첫 검사가 끝나면 글자를 넣어 두 번째 검사를 실행한다. 두 호출을 await로 순서대로 기다리므로 동시 실행도 피한다.

완료 기준: violations가 1에서 0으로, passes가 0에서 1로 바뀌고, 글자를 지우면 다시 위반이 나온다. 여기서 배열 길이는 위반 요소 수가 아니라 규칙별 결과 항목 수다.

과제 2

범위 제외와 문제 해결을 구분하기 난이도 · 초중급

목표: 같은 규칙을 실행하면서 검사 대상만 바꾸고, 결과 감소의 원인을 설명한다.

  1. doc/API.md의 범위 인자 설명과 doc/context.md를 읽는다. include·exclude는 첫 번째 인자의 범위 설정이고, runOnly는 두 번째 인자의 옵션이다.
  2. 과제 1의 JSDOM 문자열을 <main><article><button></button></article><aside><button></button></aside></main>으로 바꾼다.
  3. root와 options 선언은 유지하고, before 선언부터 출력 반복문까지를 아래 코드로 교체해 실행한다.
const whole = await axe.run(document.querySelector('main'), options);
const context = { include: ['main'], exclude: ['aside'] };
const scoped = await axe.run(context, options);

document.querySelector('article button').textContent = '저장';
const fixed = await axe.run(context, options);

const count = results => results.violations.reduce(
  (sum, rule) => sum + rule.nodes.length, 0
);
console.log(count(whole), count(scoped), count(fixed));

코드 풀이: 첫 검사는 버튼 둘을 확인하고, 다음 검사는 보조 영역을 제외한다. 마지막 검사는 포함된 버튼에 이름을 넣은 뒤 같은 범위로 다시 실행하며, nodes.length를 합쳐 위반 요소 수를 센다.

완료 기준: 출력이 2 1 0이고, 마지막에도 aside 안의 버튼은 이름이 없는 상태임을 확인한다. 제외 설정을 없애 다시 실행했을 때 위반 요소가 하나 남는 이유를 설명할 수 있어야 한다.

과제 3

규칙 선언에서 실제 판정 함수까지 추적하기 난이도 · 중급

목표: 규칙 이름, 체크 이름, 실행 함수 이름이 어떻게 연결되는지 소스와 기존 테스트로 확인한다.

  1. lib/rules/button-name.json의 selector·matches·any를 읽고, 후보 요소와 통과 조건을 각각 적는다.
  2. lib/checks/shared/button-has-visible-text.json의 evaluate를 따라 lib/checks/generic/has-text-content-evaluate.js를 읽는다. lib/checks/shared/aria-label.json도 같은 방식으로 lib/checks/shared/aria-label-evaluate.js까지 연결한다.
  3. test/checks/shared/button-has-visible-text.js에서 빈 버튼, 글자가 있는 버튼, 자식 정보가 없는 SerialVirtualNode, 빈 자식 배열이 있는 노드의 기대값을 먼저 예측한다.
  4. 아래 명령으로 해당 테스트를 실행한다. 이어서 test/checks/shared/aria-label.js를 읽어 공백만 있는 이름을 어떻게 처리하는지도 확인한다.
pnpm run test:unit --files test/checks/shared/button-has-visible-text.js
pnpm run test:unit --files test/checks/shared/aria-label.js

완료 기준: 두 테스트 파일이 통과하고, 네 입력의 반환값을 순서대로 false·true·undefined·false로 설명한다. 자식 정보가 없을 때와 자식이 없다고 확인됐을 때의 차이를 소스의 예외 처리와 연결해 적는다.

과제 4

체크 조합의 경계 조건을 회귀 테스트로 고정하기 난이도 · 심화

목표: any에서 하나가 통과해도 all의 판단 보류나 none의 금지 조건이 최종 결과를 바꿀 수 있음을 검증한다.

  1. lib/core/utils/aggregate-checks.js와 lib/core/constants.js를 읽고, test/core/utils/aggregateChecks.js를 아래 명령으로 먼저 실행해 기준 상태를 확인한다.
  2. 학습용 작업 사본의 test/core/utils/aggregateChecks.js에서 기존 최상위 describe 안에 아래 테스트를 추가한다. 그 범위에 있는 createTestCheckResults와 CANTTELL을 그대로 사용한다.
  3. 같은 형식으로 any: [true], all: [true], none: [true] 입력의 기대값을 FAIL로 검증하는 사례도 추가하고 다시 실행한다.
pnpm run test:unit --files test/core/utils/aggregateChecks.js
it('any 통과가 all의 판단 보류를 덮지 않는다', () => {
  const result = axe.utils.aggregateChecks(
    createTestCheckResults({
      any: [true],
      all: [undefined],
      none: [false]
    })
  );
  assert.equal(result.result, CANTTELL);
});

코드 풀이: 입력 배열은 각 체크가 돌려준 값이다. 첫 사례는 필요한 조건 하나를 확정하지 못한 상황이고, 두 번째 사례의 none: [true]는 금지 조건이 발견된 상황이므로 최종 실패가 된다.

완료 기준: 추가한 두 사례가 통과하고, 기대값을 일부러 PASS로 바꾸면 실패한다. 기대값을 복구한 뒤 내부의 CANTTELL이 공개 결과의 incomplete로 연결되는 과정도 설명한다.

13관련 기술 심화 학습 로드맵

6주 동안 요소의 이름에서 시작해 범위 제어, 규칙 설계, 불확실성 처리까지 넓히기

매주 작은 입력 하나를 정하고, 예상 결과를 적은 뒤 소스와 테스트로 확인한다. 읽은 파일 수보다 입력·판정·근거를 함께 남기는 것이 목표다.

주차 · 배울 개념이 저장소에서 읽을 곳주간 결과물
1주차
HTML 의미와 DOM
doc/examples/jsdom/test/a11y.js
lib/rules/button-name.json
과제 1의 수정 전·후 기록. 요소, 검사 범위, 규칙별 결과 배열의 차이를 한 문단으로 풀이한다.
2주차
ARIA와 접근 가능한 이름
lib/commons/text/accessible-text-virtual.js
lib/standards/aria-roles.js
lib/standards/aria-attrs.js
test/checks/shared/aria-label.js
빈 버튼·텍스트 버튼·비어 있지 않은 aria-label·공백뿐인 aria-label의 비교표. 이름을 얻는 경로와 규칙 통과 근거를 구분해 적는다.
3주차
범위와 실행 정책
doc/API.md
doc/context.md
lib/core/public/run.js
lib/core/base/context.js
과제 2의 전체 검사·일부 제외·수정 후 결과표. 범위 설정과 runOnly를 구분하고, 제외 항목을 다시 확인할 조건도 적는다.
4주차
선언과 실행의 분리
lib/rules/button-name.json
lib/checks/shared/button-has-visible-text.json
lib/checks/generic/has-text-content-evaluate.js
test/integration/rules/button-name/
규칙 JSON → 체크 JSON → 판정 함수 → 테스트의 연결도. 통합 테스트의 HTML 입력과 JSON 기대 결과에서 통과·위반 사례를 하나씩 연결한다.
5주차
판단 보류와 결과 집계
lib/core/utils/aggregate-checks.js
lib/core/utils/aggregate-result.js
lib/core/constants.js
test/core/utils/aggregateChecks.js
과제 4의 회귀 테스트와 조합표. any·all·none 각각에 판단 보류가 섞일 때의 결과를 예측하고 검증한다.
6주차
렌더링 의존성과 검사 한계
lib/core/base/virtual-node/
lib/checks/color/color-contrast-evaluate.js
lib/commons/color/get-contrast.js
test/checks/color/color-contrast.js
색 대비 체크가 필요로 하는 화면 정보와 판단 보류 경로의 설명서. 실제 브라우저 테스트의 입력 하나를 골라 전경색·배경색을 추적하고, 사람이 확인할 항목을 기록한다.

마지막 주에는 README.md의 jsdom 지원 범위도 다시 읽는다. 이름이나 속성 중심의 검사는 메모리 안에서 연습하기 쉽지만, 색 대비처럼 렌더링 정보가 필요한 검사는 실제 브라우저에서 확인해야 한다. 자동 결과와 수동 확인 근거를 함께 남기면 이후 접근성 회귀를 검토할 때도 사용할 수 있다.

14핵심 키워드 사전

결과를 읽고 소스를 따라갈 때 자주 만나는 말 14개
DOM · 문서 객체 모델
브라우저가 HTML의 요소와 글자를 나무 모양으로 구성한 구조다. axe-core는 이 구조에서 검사 대상을 찾으며, axe.run()에 특정 요소를 넘겨 그 부분을 검사할 수 있다.
WCAG · 웹 콘텐츠 접근성 지침
웹 콘텐츠를 다양한 사용자가 이용할 수 있도록 정리한 접근성 기준이다. 이 저장소의 규칙 JSON에는 wcag2a·wcag2aa 같은 태그가 붙어 있어 해당 기준에 연결된 규칙을 선택할 수 있다.
ARIA · 보조 기술에 전달하는 역할과 속성
요소의 역할·상태·이름 같은 정보를 보조 기술이 이해하도록 표현하는 약속이다. 이 저장소는 lib/standards/aria-roles.js와 lib/standards/aria-attrs.js의 데이터를 판정에 활용한다.
접근 가능한 이름
스크린리더 같은 보조 기술이 버튼이나 입력란을 구별할 때 사용하는 이름이다. lib/commons/text/accessible-text-virtual.js는 관련 요소 참조, aria-label, 요소의 글자 등 여러 경로를 순서에 따라 살펴 이름을 계산한다.
Rule · 규칙
어떤 요소에 어떤 조건을 적용할지 적어 둔 검사표다. lib/rules/button-name.json처럼 대상 선택자와 체크 조합, 태그, 설명을 선언하고 실행 엔진이 이를 사용한다.
Check와 evaluate · 개별 판정
규칙을 구성하는 작은 조건 검사이며, 체크 JSON의 evaluate가 실행 함수를 가리킨다. 함수의 true·false·undefined 반환값은 any·all·none 조합을 거쳐 최종 판정에 반영된다.
selector와 matches · 후보 수집과 적용 조건
selector는 검사 후보를 고르는 선택자이고, matches는 그 후보에 규칙을 적용할지 확인하는 함수다. lib/core/base/rule.js가 두 단계를 연결해 실제 검사할 노드를 추린다.
any / all / none · 체크 조합
any는 하나 이상의 조건 만족, all은 모든 조건 만족, none은 금지 조건이 발견되지 않기를 요구한다. lib/core/utils/aggregate-checks.js는 여기에 판단 보류까지 반영하므로 단순한 참·거짓 연산만으로 결과를 설명할 수 없다.
Context · 검사 범위
이번 검사에서 어디를 포함하고 제외할지 정하는 범위다. axe.run(context, options)의 첫 번째 인자에 요소나 선택자, include·exclude 객체를 전달하며 내부 처리는 lib/core/base/context.js에서 볼 수 있다.
runOnly · 실행 규칙 필터
어느 규칙을 실행할지 고르는 옵션으로, 규칙 이름이나 태그를 기준으로 제한한다. 이 저장소의 API에서는 두 번째 인자의 runOnly에 type: 'rule' 또는 type: 'tag'와 대상 목록을 지정할 수 있다.
VirtualNode · 판정용 노드 표현
실제 DOM을 감싸 속성이나 자식 정보를 일관된 방식으로 읽도록 만든 객체다. lib/core/base/virtual-node/에는 실제 노드를 감싸는 VirtualNode와 직렬화된 데이터로 구성하는 SerialVirtualNode가 있다.
incomplete · 판단 보류
문제 유무를 확정하지 못해 사람이 확인해야 하는 결과 묶음이다. 이 저장소에서는 체크의 불확실한 결과나 실행 오류가 판단 보류로 이어질 수 있으며, 내부 상수 CANTTELL과 연결된다.
impact · 영향의 심각도
발견한 문제가 사용자에게 미칠 영향을 표현하는 등급이다. lib/core/constants.js는 minor·moderate·serious·critical을 정의하며, 이는 통과·위반을 가르는 결과 분류와 별도로 읽는다.
회귀 테스트
이미 확인한 동작이 이후 변경으로 망가지지 않는지 반복해서 검사하는 테스트다. 이 저장소에서는 test/checks/의 개별 조건 검사와 test/integration/rules/의 HTML·JSON 사례를 연결해 판정의 근거를 보존한다.