ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

axe-core Context 参数完全指南:精准控制自动化无障碍测试的测试范围

axe-core Context 参数完全指南:精准控制自动化无障碍测试的测试范围 测试【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址https://gitcode.com/gh_mirrors/ax/axe-core点击查看免费下载导读本文围绕 axe-core 的context参数展开它是控制测试哪些元素、忽略哪些元素的核心工具适用于单元测试Karma/Jest/jsdom、端到端测试Selenium/Puppeteer/Playwright以及跨 iframe、跨 Shadow DOM 的复杂页面场景。读完本文你将掌握 CSS 选择器、DOM 节点、include/exclude、fromFrames、fromShadowDom全部六种上下文写法并理解 axe-core 底层的上下文归一化与包含/排除判定机制从而让每一次自动化无障碍扫描精准命中目标区域。一、什么是 context先理解六种合法输入axe.run(context, options, callback)的第一个参数context可选定义分析的 DOM 范围。默认情况下axe.run会测试整个document而 context 允许你精确指定测试与忽略的部分。根据 API 文档context 可以传入以下六种形式之一单个元素引用如document.getElementById(content)NodeList如document.querySelectorAll(...)的返回值CSS 选择器或选择器数组含exclude和/或include属性的对象含fromFrames属性的对象含fromShadowDom属性的对象。在源码层面context 的类型判定由 lib/core/utils/is-context.js 完成isContextSpec判断字符串 / Node / fromFrames 对象 / fromShadowDom 对象 / 类数组五种可解析形态isContextProp以及带include或exclude键的对象isContextObject。axe.run的参数归一化见 lib/core/public/run/normalize-run-params.js若第一个参数不是合法 context则自动回退为document并把参数依次后移。也就是说axe.run()、axe.run(options)、axe.run(callback)全部等价于对整页进行测试。补充context 对象还可以携带page、initiator、focusable、size等布尔/对象字段用于运行部分测试partial result等进阶场景详见 lib/core/base/context.js。常规用法下无需关心这些字段。二、测试特定元素CSS 选择器与选择器数组当传入 CSS 选择器或选择器数组时axe 只测试匹配这些选择器的元素及其内部全部内容// 测试每个 nav 与 main 元素以及它们内部的所有内容 await axe.run(nav, main); // 测试每个 nav、每个带 sideBar 类的元素、 // 以及带 #header id 的元素连同它们的所有内容 await axe.run([nav, .sideBar, #header]);实用技巧当页面由多个团队协作开发时可以给自己的模块统一打上一个独特 class然后让 axe 只测试自己团队负责的区块避免其他团队尚未完成的区域干扰扫描结果。在浏览器驱动 API 中使用.include()axe-core 通常经由浏览器驱动 APISelenium、Puppeteer、Playwright使用这类 API 并不直接接收 context 对象而是通过AxeBuilder的.include()方法实现同样的效果具体用法请查阅对应 API 的文档典型写法如下const axe new AxeBuilder({ page }); // 测试每个 nav 与 main 元素以及它们内部的所有内容 axe.include(nav, main);仓库中的 Puppeteer 示例doc/examples/puppeteer/axe-puppeteer.js与 CDP 示例doc/examples/chrome-debugging-protocol/axe-cdp.js展示了这一模式的完整接入方式。三、测试 DOM 节点Node、NodeList 与数组axe 可以直接接收原生 DOM 元素进行测试前提是这些元素必须已挂载到 DOM 树上document或其可见子树中。这在 Karma、Jest 等测试环境中非常实用// 测试单个 DOM 节点 const img document.createElement(img); document.body.appendChild(img); await axe.run(img); // 测试 NodeList例如 document.querySelectorAll 的返回值 const nodes document.querySelectorAll(main, header); await axe.run(nodes); // 测试节点数组 const navbar document.getElementById(navbar); const cookiePopup document.getElementById(cookie-popup); await axe.run([navbar, cookiePopup]);源码层面normalizeContext对window.Node实例不做任何包装见 normalize-context.js 的注释 Nodes must not be wrapped in an array而parseSelectorArray会通过getNodeFromTree把节点映射到扁平树上的对应节点见 parse-selector-array.js。测试 test/core/base/context.js 覆盖了单个节点、数组节点、嵌套 div、含控件的表单等场景并验证了未匹配的引用会被移除这一行为。组件框架React / Vue / Angular由于 axe 必须依赖真实 DOM 才能测试React、Vue、Angular 等组件必须先渲染、再测试。以下示例展示渲染MyAppReact 组件后交给 axe 测试const appRoot document.getElementById(app); ReactDOM.createRoot(appRoot).render(MyApp); await axe.run(appRoot);重要限制组件测试库如 Enzyme同时提供render与shallow方法。由于 axe 需要完整渲染并挂载到 DOM 树上的真实结构无法测试用shallow浅渲染构建的组件。仓库中的 Jest React 示例doc/examples/jest_react/link.test.js展示了渲染后进行测试的完整流程。四、排除元素exclude与include对象页面上总有一些开发者无法控制的区域第三方广告、外嵌视频等。此时可以用带exclude属性的对象让 axe 跳过这些元素。exclude接受与 include 完全相同的输入类型包括 CSS 选择器和 DOM 节点// 测试除广告横幅以外的所有内容 await axe.run({ exclude: .ad-banner }); // 测试除这些 DOM 节点以外的所有内容NodeList 同样可用 const youtubeVids document.querySelectorAll(iframe[src^youtube.com]); await axe.run({ exclude: youtubeVids });在 Playwright 等 API 中对应方法是AxeBuilder的.exclude()const axe new AxeBuilder({ page }); // 测试除广告横幅与 YouTube 帧以外的所有内容 axe.exclude(.ad-banner, iframe[src^youtube.com]);隐式 include一切选择器都是 include当你直接传入 CSS 选择器或 DOM 节点数组时axe 将其视作隐式的 include。上一节所有例子都可以改写为带include属性的对象二者行为完全等价await axe.run(main); // 等同于 await axe.run({ include: main });当 axe 未收到任何 include无论是显式还是隐式时默认使用documentawait axe.run({ exclude: .ad-banner }); // 等同于 await axe.run({ include: document, exclude: .ad-banner });这一默认行为在源码中有明确体现normalize-context.js 中include.length 0时自动include.push(document)测试 test/core/base/context.js 验证了默认 include 为 document与空 include 回退为 document两条路径。同时使用include与exclude想测试页面特定区块、又跳过该区块中的部分内容时可以同时传入include与excludeinclude决定测什么exclude在已 include 的区域内进一步排除。下面的例子测试main和footer元素但跳过其中所有.ad-bannerawait axe.run({ include: [main, footer], exclude: .ad-banner });优先级规则重要当一个元素同时被 include 和 exclude 命中时命中最近祖先的选择器优先。例如某节点的祖父被 include 但父被 exclude则该节点被排除反之祖父被 exclude 但父被 include则该节点被纳入。这条规则正是源码 is-node-in-context.js 的实现逻辑分别收集包含该节点的 include 候选与 exclude 候选取各自最深的节点getDeepest若最深的 exclude 包含最深的 include 则排除否则包含第 10-22 行。这也是 context 判定所有包含/排除关系的最终裁决函数。五、从先前测试结果中选择target属性复用include或exclude可以使用先前测试结果中的节点从而跳过已发现的问题或在你修复问题时反复重测同一批节点。这依赖 axe 结果中的target属性// 从 color-contrast 违规中取出 target 属性 const violation priorResult.violations.find( ({ id }) id color-contrast ); const targets violation.nodes.map(issue issue.target); // 只测试先前 color-contrast 违规的节点 await axe.run(targets); // 或者从本次测试中排除这些节点 await axe.run({ exclude: targets });重要限制不能把单个target属性直接传给 axe必须包裹在数组中。这是因为 axe-core 的target属性本身就是数组例如[#blog-comments, [#userComments, .commentBody]]详见下文隐式 frame 与 shadow DOM 选择一节。六、限制帧测试fromFrames选择器对象在 iframe 内包含/排除特定区块需要使用fromFrames选择器对象其属性值为选择器数组第一个选择器选中 frame 元素最后一个选择器选中要包含/排除的目标元素。下面的例子测试所有#paymentFrameframe/iframe 内的form元素// 测试每个 #paymentFrame frame 或 iframe 内的每个 form axe.run({ fromFrames: [#paymentFrame, form] });嵌套帧由于 axe 会递归测试帧嵌套多少层就需要多少级选择器。下面的例子测试#outeriframe 内的#inneriframe 内的form// 测试 #outer 内、#inner 内、以及其中的 form axe.run({ fromFrames: [iframe#outer, iframe#inner, form] });在 include / exclude 中组合 fromFramesfromFrames对象可以作为exclude或include的值可以单独出现也可以与其他选择器一起放在数组中。下面的例子同时排除顶层窗口的.ad-banner和第一层 iframe 内的.ad-banner// 跳过所有 .ad-banner以及 iframe 内的 .ad-banner axe.run({ exclude: [ .ad-banner, { fromFrames: [iframe, .ad-banner] } ] });fromFrames同样可以同时用于include和exclude。下面的例子测试#paymentiframe 内的form但排除该form内的.ad-banneraxe.run({ include: { fromFrames: [iframe#payment, form] }, exclude: { fromFrames: [iframe#payment, form .ad-banner] } });注意fromFrames属性不能与include/exclude放在同一个对象上。源码 normalize-context.js 对此有显式断言fromFrames必须用在 include 或 exclude 内部否则抛出Invalid context; fromFrames must be used inside include or exclude...错误test/core/base/context.js 对fromFrames、fromShadowDom与 include/exclude 同层的非法写法均有对应的抛错测试。七、限制 Shadow DOM 测试fromShadowDom选择器对象包含/排除 Shadow DOM 树中的特定区块使用fromShadowDom选择器对象。它与fromFrames工作方式类似属性值为字符串数组第一个选择器选中 shadow DOM host 元素最后一个选择器选中目标元素。下面的例子测试.app-header元素所挂 shadow DOM 树中的#search表单// 测试每个 app-header shadow DOM 树中的每个搜索表单 axe.run({ fromShadowDom: [.app-header, form#search] });嵌套 Shadow DOM选择嵌套 shadow DOM 树中的元素时每层嵌套都需要一个选择器。下面的例子测试app-root自定义元素 shadow DOM 树内、.header元素 shadow DOM 树中的#search元素// 测试每个 app-root 内、每个 .header 内的 #search axe.run({ fromShadowDom: [app-root, .header, #search] });Light DOM 与 slot 的特殊规则要选择嵌套 shadow DOM 树中位于 light DOM 的元素例如使用了slot元素时选择器应当解析到该元素在light DOM 中的位置而非其在嵌套 shadow DOM 树中的最终渲染位置。例如要选中下面 DOM 结构中的deeply-nested-elementcustom-element #shadow-root nested-element #shadow-root slot deeply-nested-element #shadow-root slot button#target正确的选择器是[custom-element, deeply-nested-element]而不是[custom-element, nested-element, deeply-nested-element]——因为deeply-nested-element位于custom-element的 light DOM 中它是nested-element的内容投影而非nested-element的子树。在 include / exclude 中组合 fromShadowDomfromShadowDom对象可以作为exclude或include的值单独使用或与其他选择器组成数组。下面的例子排除footer以及blog-comments自定义元素 shadow DOM 树内的所有.comment元素// 跳过 footer以及 blog-comments shadow DOM 树内的所有 .comment 元素 axe.run({ exclude: [ footer, { fromShadowDom: [blog-comments, .comment] } ] });同样fromShadowDom可以同时用于include和exclude。下面的例子测试#root元素 shadow DOM 内的app-footer自定义组件但排除app-footer自身 shadow DOM 树内的.ad-banneraxe.run({ include: { fromShadowDom: [#root, app-footer] }, exclude: { fromShadowDom: [#root, app-footer, .ad-banner] } });注意与fromFrames相同fromShadowDom不能与include/exclude放在同一个对象上。Slotted 元素axe 基于扁平化 DOM 树flattened DOM tree判断元素的包含/排除。因此当一个 shadow DOM 节点被选中时所有通过slot元素插入的后代节点也会一并被选中。这与 test/core/base/context.js 中light DOM 选择不会匹配 shadow DOM 节点的测试互为印证扁平树决定了实际匹配集合。八、组合 Shadow DOM 与 Frame ContextfromShadowDom可以作为选择器嵌入fromFrames数组中从而选中位于 shadow DOM 树内的 iframe或位于 iframe 内的 shadow DOM 树。下面的例子测试#appRootshadow DOM 树内每个iframe中的main元素await axe.run({ fromFrames: [ { fromShadowDom: [#appRoot, iframe] }, main ] });这些选择器同样可以用于include或exclude。下面的例子排除footer以及#blog-commentsiframe 内、#userCommentsshadow DOM 树中的所有.commentBody元素await axe.run({ exclude: [ footer, { fromFrames: [ iframe#blog-comments, { fromShadowDom: [#userComments, .commentBody] } ] } ] });注意方向性限制即使 iframe 位于 shadow DOM 树内部fromShadowDom选择器对象必须作为fromFrames的组成部分出现反过来在fromShadowDom内部嵌套fromFrames不生效且会报错。这一点在源码 normalize-context.js 中有对应断言shadow selector must be inside fromFrame instead。九、隐式 Frame 与 Shadow DOM 选择target与嵌套数组在 axe 早期版本中嵌套数组是包含/排除 iframe 内元素的唯一方式至今仍被支持也是 axe 内部的实际工作方式。这种嵌套数组语法体现在target属性中。例如#blog-commentsiframe 内、#userCommentsshadow DOM 树中的.commentBody元素其 target 可能是result await axe.run(); result.violations[0].nodes[0].target; // [#blog-comments, [#userComments, .commentBody]]把target传入 axe 时必须再包裹一层数组形成三层嵌套结构最外层数组用于承载多个选择器中间层数组用于选入帧内层数组可选用于选择 shadow DOM 树内的元素。虽然这种语法仍然受支持但官方建议改用fromFrames与fromShadowDom对象选择器语义更清晰且无需为了包一层空数组而嵌套多余的层级。从源码看normalizeContext会把带fromFrames/fromShadowDom键的对象解包为内部的标准嵌套数组见 normalize-context.jsparseSelectorArray再对帧选择器逐级解析并推入 frame 上下文见 parse-selector-array.js——这也是对象语法与嵌套数组语法等价的底层原因。十、底层原理context 的归一化、解析与判定链路为了让上述所有语法生效axe-core 构建了一条清晰的内部处理链从axe.run到最终判定依次经过参数归一化normalize-run-params.js 判定第一个参数是否为合法 context非法时回退到document上下文归一化normalize-context.js 将裸选择器 / 节点 / 数组包装成{ include, exclude }把fromFrames、fromShadowDom解包为标准选择器数组并对非法组合如 fromFrames 与 include 同层、fromShadowDom 嵌套 fromFrames抛出带doc/context.md指引的断言错误选择器解析parse-selector-array.js 将归一化后的选择器数组逐级解析为扁平树节点并把命中 iframe 的选择器递归分配到对应的frameContext包含/排除裁决is-node-in-context.js 基于最深 include 与最深 exclude 的包含关系决定每个节点最终是否进入测试范围帧上下文收集get-frame-contexts.js 在运行部分测试时axe.runPartial提取{ frameSelector, frameContext }列表并通过options.iframes false关闭帧测试。核心的Context类在 lib/core/base/context.js 中完成组装克隆 spec、归一化、解析 include/exclude、收集可见 iframe、判断是否为整页上下文include仅含document.documentElement时视为整页并按文档顺序对 include 节点排序。测试层面test/core/base/context.js 以 1100 余行的覆盖验证了各类输入形态单选择器、帧选择器、字符串数组、节点引用、NodeList、jQuery 类数组对象、ShadowRoot 引用、混合数组以及无匹配时报错No elements found for include in page Context对应 context.js 的校验逻辑。结语选择最适合你场景的 context 写法场景推荐写法只测页面某几个区块axe.run([nav, .sideBar, #header])测某区块但跳过其中部分axe.run({ include: [...], exclude: ... })测已渲染的组件/DOM 节点axe.run(appRoot)或axe.run(nodes)进入 iframe 内测试axe.run({ fromFrames: [iframe#payment, form] })进入 Shadow DOM 内测试axe.run({ fromShadowDom: [.app-header, form#search] })帧内套 Shadow DOMaxe.run({ fromFrames: [{ fromShadowDom: [...] }, main] })复用上次测试的违规节点axe.run(priorViolationNodes.map(n n.target))掌握 context 参数意味着你可以把每次无障碍扫描的资源精确投放到自己负责的代码区域规避第三方内容与未完成模块的干扰也能在修复阶段对同一批节点反复回归。结合 API 文档 中的 context 章节与运行参数runOnly、rules等组合使用即可搭建一套覆盖单元、集成与端到端三层的精准无障碍测试体系。赞分享测试【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址https://gitcode.com/gh_mirrors/ax/axe-core点击查看免费下载相关推荐Stencil组件无障碍测试axe-core与自动化测试集成Stencil组件无障碍测试axe core与自动化测试集成 无障碍测试的痛点与解决方案 你是否还在手动检查每个组件的无障碍属性是否担心上线后因WCAG合规开发工具前端前端构建AIRealNet实战指南5个步骤部署你的第一个AI图像检测应用AIRealNet实战指南5个步骤部署你的第一个AI图像检测应用 在AI生成图像技术飞速发展的今天如何快速区分AI生成图像与真实人类照片成为了一个迫切需求。OpenResume无障碍自动化测试axe-core与Cypress集成OpenResume无障碍自动化测试axe core与Cypress集成 项目无障碍测试现状分析 OpenResume作为一款开源简历构建工具其用户群体广泛前端上一篇MGV2000-CW 刷Armbian改服务器教程创维IPTV盒子变身7×24小时小主机下一篇MM-Pose 全身关键点估计实战基于 ResNet 的 Top-Down 热图方法在 COCO-WholeBody 上的配置与基准创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表