- 测试
【免费下载链接】axe-core
Accessibility engine for automated Web UI testing
本篇指南围绕 axe-core 官方文档 doc/examples/test-patterns.md 展开,系统梳理 axe-core(自动化 Web UI 无障碍测试引擎)的测试约定与分层架构:从 commons 工具函数、check evaluate 的单元测试,到基于 HTML + JSON 配对文件的规则集成测试、完整页面集成测试与虚拟规则测试。读完本文,你将掌握 axe-core 测试文件的组织位置、每种测试的固定写法、断言方式与常见陷阱,可以直接照着模式为新的规则或检查项补齐测试。
测试分层总览:单元测试与集成测试的分工
axe-core 的测试体系分为两大层:单元测试与集成测试。单元测试直接调用被测试函数(commons 工具函数或 check 的 evaluate 方法),不经过完整的规则引擎;集成测试则通过axe.run()或axe.runVirtualRule()走真实审计流程,验证规则在真实 DOM / 虚拟节点上的最终行为。
两者依赖同一套测试基础设施:test/testutils.js中的axe.testUtils命名空间。该文件在测试启动时被注入全局,提供了MockCheckContext、queryFixture、queryShadowFixture、getCheckEvaluate、checkSetup等核心方法,并为整个测试套件注册全局 mochabeforeEach/afterEach钩子(registerHooks),在每条用例前后重置 axe 状态并清空#fixture元素。编写任何测试前,建议先阅读 test/testutils.js 了解这些工具的行为。
单元测试一:Commons 工具函数
Commons 是 axe-core 对外暴露的公共工具库(如axe.commons.text、axe.commons.dom、axe.commons.color等),对这些纯函数编写单元测试时,直接使用 mocha 的describe/it与 chai 的assert断言即可,不需要 fixture 或虚拟节点。
官方文档给出的text.sanitize示例体现了 commons 测试的典型写法:
describe('text.sanitize', function () { it('should collapse whitespace and trim', function () { assert.equal(axe.commons.text.sanitize('\thi\t'), 'hi'); assert.equal(axe.commons.text.sanitize('\t\nhi \t'), 'hi'); assert.equal(axe.commons.text.sanitize('hello\u00A0there'), 'hello there'); }); it('should accept null', function () { assert.equal(axe.commons.text.sanitize(null), ''); }); });要点:
- 通过全局
axe.commons.*直接访问工具函数,无需任何 DOM 准备; - 一个
it内可包含多个assert.equal,覆盖不同输入(这里覆盖了制表符、换行、不间断空格\u00A0与null边界); - 测试文件通常放在 test/commons 目录下与源码一一对应的子目录中(如
test/commons/text/),仓库中已有大量同类示例可参考。
单元测试二:Check 的 evaluate 方法
与 commons 不同,check 的 evaluate 方法需要真实的this上下文(用于收集data()、relatedNodes()、异步结果等),因此单元测试必须借助axe.testUtils.MockCheckContext()构造上下文,再通过axe.testUtils.getCheckEvaluate(checkId)拿到包装后的 evaluate 函数,用.call()绑定上下文调用。
官方文档以aria-allowed-attr为例:
describe('aria-allowed-attr', function () { const fixture = document.getElementById('fixture'); const checkContext = axe.testUtils.MockCheckContext(); afterEach(function () { checkContext.reset(); }); it('should return true if all ARIA attributes are allowed', function () { const vNode = queryFixture( '<div role="textbox" aria-placeholder="foo" id="target"></div>' ); assert.isTrue( axe.testUtils .getCheckEvaluate('aria-allowed-attr') .call(checkContext, vNode.actualNode, {}, vNode) ); }); });这里有几层细节值得展开:
queryFixture(html):来自test/testutils.js的工具方法,它把 HTML 注入#fixture元素(testUtils.fixtureSetup),随后执行axe.setup()构建虚拟树,最后按默认选择器#target找到目标节点并返回其VirtualNode(在 test/testutils.js 中定义,找不到目标时断言失败并给出明确提示)。因此被测 HTML 片段中必须包含id="target"的元素。MockCheckContext():返回一个模拟的 check 上下文对象,包含_data、_relatedNodes、_onAsync字段以及async()、data()、relatedNodes()、reset()方法(实现见 test/testutils.js)。每次用例结束后调用checkContext.reset()清空上一次的data、relatedNodes与异步回调,避免用例间相互污染。getCheckEvaluate(checkId, testOptions):返回 evaluate 包装器,它会在调用前合并 check 的默认选项(check.getOptions(options)),并在调用后自动校验返回结果是否有对应的 pass / fail / incomplete 消息(从axe._audit.data.checks[checkId].messages中查证)。这意味着即使你的用例只关心返回值,消息完整性也会被隐式验证。对只在规则none数组中使用的 check,其消息键与结果相反(返回false时查pass消息),包装器会自动处理该语义。- 调用签名:
.call(checkContext, vNode.actualNode, {}, vNode)三个参数分别是真实 DOM 节点、选项对象(这里为空{})、虚拟节点——这正是 check evaluate 在引擎内的标准调用签名(见 lib/checks/aria/aria-allowed-attr-evaluate.js 等实现)。
单元测试三:Shadow DOM 场景
当被测逻辑需要覆盖 Shadow DOM 内容时,使用queryShadowFixture同时准备**宿主(light DOM)与影子边界(shadow DOM)**两份 HTML:
it('should work with Shadow DOM', function () { const vNode = queryShadowFixture( '<div id="host"></div>', '<div role="button" id="target">Test</div>' ); // Test your function against the shadow DOM content });queryShadowFixture(content, shadowContent, targetSelector)的实现要点(见 test/testutils.js):
- 将
content注入 fixture,在容器上调用attachShadow({ mode: 'open' }),再把shadowContent写入 shadow root; - 目标查询优先在 shadow root 内查找
#target,找不到再回退到 light DOM;targetSelector也可传对象{ shadow, target }自定义两边的选择器; - 挂载完 shadow DOM 后才执行
axe.setup(),确保扁平化树(composed tree)包含影子内容,返回的仍是目标节点的VirtualNode。
需要说明的是:随着浏览器对**声明式 Shadow DOM(declarative shadow DOM)**支持度的提升,test/testutils.js 中已通过正则/<template\s+shadowrootmode\s*=\s*(['"]?)open\1/自动检测<template shadowrootmode="open">片段,并在queryFixture、checkSetup中自动改用setHTMLUnsafe解析、按"从深到浅"顺序在多层 shadow root 中定位#target。因此新写的 Shadow DOM 用例推荐直接用声明式写法配合queryFixture,旧的queryShadowFixture/shadowCheckSetup在源码注释中已被标记为 deprecated。
集成测试:规则的 HTML + JSON 配对文件
规则(rule)级别的行为变更需要配套集成测试。官方文档明确了两类放置位置:
- mocha 托管的规则测试:
test/integration/rules/<rule-name>/目录,存放规则的 HTML 与 JSON 配对文件; - 完整 HTML 页面测试:
test/integration/full/<rule-name>/目录,面向需要完整页面上下文的规则(如 landmark、页面级规则)。
文档以aria-allowed-attr为例,给出了 HTML 与 JSON 配对文件的格式。
HTML 文件(如aria-allowed-attr.html)
<div role="textbox" aria-placeholder="foo" id="pass1">Valid</div> <div role="button" aria-placeholder="invalid" id="fail1">Invalid</div>实际仓库中,这一目录的命名约定更细:test/integration/rules/aria-allowed-attr/下拆分为passes.html、failures.html、incomplete.html三份 HTML(分别对应通过、违规、不完整三类预期),每份文件内部元素以pass0、pass1… 与fail1、fail2… 等 id 递增编号,测试框架按 JSON 中列出的 id 选择器逐一断言(可对照 test/integration/rules/aria-allowed-attr/passes.html 与 test/integration/rules/aria-allowed-attr/failures.json)。
JSON 文件(如aria-allowed-attr.json)
{ "description": "aria-allowed-attr test", "rule": "aria-allowed-attr", "violations": [["#fail1"]], "passes": [["#pass1"]] }字段说明:
description:测试描述;rule:被测规则 id,必须与lib/rules/<rule-name>.json中注册的规则 id 一致;passes/violations:通过 / 违规元素的 id 数组;- 元素 id 使用axe selector 数组格式(数组的每个元素依次表示一层 DOM 边界)。
iframe 内元素的定位格式
当目标元素位于 iframe 内部时,选择器必须写出跨边界路径——先指向 iframe 元素本身,再指向其中的元素:
{ "violations": [["iframe", "#fail-inside-iframe"]] }这是 axe selector 数组格式的典型应用:数组第 1 项是 iframe 的 CSS 选择器,第 2 项是 iframe 文档内的目标选择器,多层 iframe 依此类推。相关实现可参考test/testutils.js中的shadowQuerySelector与runPartialRecursive(后者会递归遍历各 frame 的上下文分别调用axe.runPartial)。
完整页面集成测试(full-page)
对 landmark 规则、页面级规则这类必须运行在完整 HTML 文档上的规则,test/integration/rules/<rule>/中注入片段的方式不够用,应改用test/integration/full/<rule-name>/。这些测试针对完整 HTML 文档运行axe.run(),而不是注入碎片。
仓库中的典型结构是每个场景一对*.html+*.js文件,例如 test/integration/full/landmark-banner-is-top-level/ 下同时存在landmark-banner-is-top-level-pass.html/-pass.js与landmark-banner-is-top-level-fail.html/-fail.js。JS 文件在before钩子中等待嵌套 frame 加载完成后执行axe.run,再分别断言 violations / passes / inapplicable / incomplete 的数量,例如 test/integration/full/landmark-banner-is-top-level/landmark-banner-is-top-level-pass.js:
describe('landmark-banner-is-top-level test pass', () => { let results; before(done => { axe.testUtils.awaitNestedLoad(() => { axe.run( { runOnly: { type: 'rule', values: ['landmark-banner-is-top-level'] } }, (err, r) => { assert.isNull(err); results = r; done(); } ); }); }); // ... });关键工具是axe.testUtils.awaitNestedLoad()(test/testutils.js):它会等待当前文档readyState === 'complete',并递归等待所有嵌套 iframe 加载完成,再执行回调——对于含 frame 的页面级测试这是必需的等待手段。断言语义与规则集成测试一致:results.violations、results.passes[0].nodes、results.inapplicable、results.incomplete分别对应违规、通过、不适用、不完整四类结果。test/integration/full/aria-hidden-body/pass.js(test/integration/full/aria-hidden-body/pass.js)是另一个更精简的同类范例。
虚拟规则测试(virtual rules)
某些规则不需要真实 DOM,仅凭虚拟节点(serialized node data)即可运行,这类规则应在虚拟规则测试中验证其与axe.run()在序列化节点数据上的兼容性。
官方文档描述的目录为test/virtual-rules/;在当前仓库中,对应实现位于test/integration/virtual-rules/(内含 48 个*.js测试文件),并由 test/test-virtual-rules.js 通过 glob 批量加载运行。每个规则一个describe,核心 API 是axe.runVirtualRule(ruleId, vNodeData):
describe('aria-allowed-attr virtual-rule', () => { it('should pass for required attributes', () => { const results = axe.runVirtualRule('aria-allowed-attr', { nodeName: 'div', attributes: { role: 'checkbox', 'aria-checked': true } }); assert.lengthOf(results.passes, 1); assert.lengthOf(results.violations, 0); assert.lengthOf(results.incomplete, 0); }); // ... });虚拟节点数据以nodeName+attributes描述元素(test/integration/virtual-rules/aria-allowed-attr.js)。完整的aria-allowed-attr虚拟规则测试覆盖了:必需属性通过、允许属性通过、非法(未注册)属性通过、无 role 时的全局属性通过、无 role 时的非全局属性违规、role 不允许的属性违规、隐式 role(<a href="#">)下的属性违规、通过axe.configure({ standards: { ariaAttrs: {...} } })注入 unsupported 属性后的违规、自定义元素(custom-elm1)上的非全局属性返回 incomplete,以及废弃属性(aria-grabbed)返回 incomplete、废弃属性叠加违规属性时判违规等边界场景。
虚拟规则测试的价值在于:它不依赖浏览器 DOM 渲染,直接以节点数据驱动完整审计流水线,能快速验证规则在"纯数据"形态下的判定逻辑,是补充真实 DOM 集成测试的高性价比手段。因此文档建议:为合适的规则新增或同步更新虚拟规则测试。
测试约定速查与最佳实践
综合官方文档与仓库实现,编写 axe-core 测试时可遵循以下约定:
- commons 函数测试:放在
test/commons/对应子目录,直接用assert断言函数返回值,无需 DOM。 - check evaluate 测试:用
MockCheckContext()管理上下文,queryFixture()注入 HTML 并定位#target,getCheckEvaluate(checkId).call(ctx, node, options, vNode)执行,afterEach中checkContext.reset()。 - Shadow DOM:优先用声明式
<template shadowrootmode="open">配合queryFixture;需要传统写法时使用queryShadowFixture。 - 规则集成测试:在
test/integration/rules/<rule>/下放置 HTML + JSON 配对文件(passes/failures/incomplete),id 用 axe selector 数组格式,iframe 内元素写["iframe", "#target"]。 - 页面级规则:放入
test/integration/full/<rule>/,用awaitNestedLoad等待加载后用axe.run断言各类结果数量。 - 虚拟规则:在
test/integration/virtual-rules/中为规则添加axe.runVirtualRule用例,覆盖 pass / violation / incomplete 三类结果。
这套模式覆盖了从最小单元到完整页面的全部测试层级。任何改动(尤其是规则与 check 行为变更)都应按上述约定补齐对应测试,并在本地跑通对应测试文件后再提交,以保持 axe-core 高覆盖率的测试传统。
- 测试
【免费下载链接】axe-core
Accessibility engine for automated Web UI testing
相关推荐
Bottlerocket 测试指南:从单元测试到 TestSys 集成测试的完整实践
Bottlerocket 测试指南:从单元测试到 TestSys 集成测试的完整实践 本指南以 Bottlerocket 仓库根目录下的 TESTING.md
操作系统云原生安全Camunda Platform 测试指南:从单元测试规则到集成测试与多数据库验证的完整实践
Camunda Platform 测试指南:从单元测试规则到集成测试与多数据库验证的完整实践 本文是 Camunda Platform(camunda bpm
后端工作流自动化流程编排5大主流平台社交媒体数据采集解决方案:MediaCrawler-new深度解析
5大主流平台社交媒体数据采集解决方案:MediaCrawler new深度解析 在数据驱动的互联网时代,社交媒体已成为信息获取和市场洞察的重要来源。然而,面对小
网页爬虫后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考