[{"data":1,"prerenderedAt":1733},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci\u002F":314,"content-navigation":1581},[4,84,88,216],{"title":5,"path":6,"stem":7,"children":8},"Core Accessibility Principles For Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks","core-accessibility-principles-for-modern-frameworks",[9,12,18,24,36,48,66,78],{"title":10,"path":6,"stem":11},"Core Accessibility Principles for Modern Frameworks","core-accessibility-principles-for-modern-frameworks\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Accessible Color Contrast & Theming","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming","core-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming\u002Findex",[17],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Accessible Form Validation & Error States","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states","core-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Focus Management Strategies for SPAs","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Findex",[29,30],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Handling Focus Restoration After Dynamic Route Changes","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Keyboard Navigation Patterns for Modals","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Findex",[41,42],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Building Accessible Dropdowns Without External UI Kits","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits\u002Findex",[47],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Reduced Motion & Animation Accessibility","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Findex",[53,54,60],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Accessible Loading Skeletons and Spinners","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Faccessible-loading-skeletons-and-spinners","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Faccessible-loading-skeletons-and-spinners\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Respecting prefers-reduced-motion in React and CSS","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Frespecting-prefers-reduced-motion-in-react-and-css","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Frespecting-prefers-reduced-motion-in-react-and-css\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Screen Reader Compatibility Testing","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Findex",[71,72],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Testing ARIA Live Regions with Jest and Testing Library","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Semantic HTML vs ARIA in Component Trees","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees","core-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},"Modern Framework Accessibility","\u002F","index",{"title":89,"path":90,"stem":91,"children":92},"React Nextjs Accessibility Patterns","\u002Freact-nextjs-accessibility-patterns","react-nextjs-accessibility-patterns",[93,96,108,132,156,162,180,204],{"title":94,"path":90,"stem":95},"React & Next.js Accessibility Patterns","react-nextjs-accessibility-patterns\u002Findex",{"title":97,"path":98,"stem":99,"children":100},"Accessible Component Libraries in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Findex",[101,102],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Building Accessible Tabs in React Without Radix UI","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"Accessible Data Tables & Grids in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Findex",[113,114,120,126],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Accessible Pagination for React Data Tables","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Faccessible-pagination-for-react-data-tables","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Faccessible-pagination-for-react-data-tables\u002Findex",[119],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Building a Sortable Accessible Data Table in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fbuilding-a-sortable-accessible-data-table-in-react","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fbuilding-a-sortable-accessible-data-table-in-react\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Virtualizing Long Lists Accessibly in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fvirtualizing-long-lists-accessibly-in-react","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fvirtualizing-long-lists-accessibly-in-react\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"Dynamic Content & State Announcements","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Findex",[137,138,144,150],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Accessible Toast Notifications in React","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Announcing Client-Side Route Changes in React","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"React Context for Accessibility Preferences","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences\u002Findex",[155],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Form Handling with React Hook Form & A11y","\u002Freact-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y","react-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Next.js App Router Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Findex",[167,168,174],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Skip Links in Next.js App Router","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Next.js Dynamic Imports & Keyboard Navigation","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation\u002Findex",[179],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"React Hooks for Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Findex",[185,186,192,198],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Building a useAnnouncer Hook for Live Regions","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fbuilding-a-useannouncer-hook-for-live-regions","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fbuilding-a-useannouncer-hook-for-live-regions\u002Findex",[191],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":196},"Fixing Focus Trap Issues in React Portals","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals\u002Findex",[197],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":202},"Making React useEffect Accessible for Screen Readers","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers\u002Findex",[203],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":208},"Server Components & Client-Side Interactivity","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Findex",[209,210],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":214},"Accessible Modals in Next.js 14 Server Components","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components\u002Findex",[215],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":220},"Testing And Automating Accessibility","\u002Ftesting-and-automating-accessibility","testing-and-automating-accessibility",[221,224,242,260,278,296],{"title":222,"path":218,"stem":223},"Testing & Automating Accessibility","testing-and-automating-accessibility\u002Findex",{"title":225,"path":226,"stem":227,"children":228},"Accessibility Audits with Lighthouse","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Findex",[229,230,236],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Interpreting Lighthouse Accessibility Scores","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Finterpreting-lighthouse-accessibility-scores","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Finterpreting-lighthouse-accessibility-scores\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Setting Lighthouse CI Accessibility Budgets","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Fsetting-lighthouse-ci-accessibility-budgets","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Fsetting-lighthouse-ci-accessibility-budgets\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Automated Accessibility Testing with axe-core","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Findex",[247,248,254],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Catching Color Contrast Failures with axe-core","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fcatching-color-contrast-failures-with-axe-core","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fcatching-color-contrast-failures-with-axe-core\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Writing Custom axe-core Rules","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fwriting-custom-axe-core-rules","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fwriting-custom-axe-core-rules\u002Findex",[259],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"Component Testing with jest-axe","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Findex",[265,266,272],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Debugging jest-axe Violations in CI","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Testing React Components with jest-axe","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"End-to-End Accessibility Testing with Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Findex",[283,284,290],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Asserting Focus Order in Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright\u002Findex",[289],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Keyboard Navigation Tests in Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Gating Accessibility in CI\u002FCD Pipelines","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Findex",[301,302,308],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Accessibility Regression Testing in GitHub Actions","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Faccessibility-regression-testing-in-github-actions","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Faccessibility-regression-testing-in-github-actions\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Failing Pull Requests on axe Violations","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations\u002Findex",[313],{"title":309,"path":310,"stem":311},{"id":315,"title":267,"body":316,"date":1574,"description":1575,"extension":1576,"image":1574,"meta":1577,"modifiedAt":1574,"navigation":421,"noindex":1578,"path":268,"publishedAt":1574,"seo":1579,"stem":269,"updatedAt":1574,"__hash__":1580},"content\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci\u002Findex.md",{"type":317,"value":318,"toc":1560},"minimark",[319,323,349,360,365,380,561,598,601,605,612,855,865,867,871,885,1026,1045,1047,1051,1058,1220,1227,1229,1233,1236,1269,1272,1316,1321,1323,1327,1330,1359,1383,1385,1389,1447,1449,1453,1456,1458,1462,1479,1500,1524,1537,1541,1556],[320,321,267],"h1",{"id":322},"debugging-jest-axe-violations-in-ci",[324,325,326,327,331,332,335,336,339,340,344,345,348],"p",{},"A jest-axe test that is green on your laptop and red in CI is almost never a flaky framework—it is a timing, scope, or environment difference that your local run happened to paper over. This guide is a triage manual: how to read the ",[328,329,330],"code",{},"violations"," array axe returns, how to stop testing the DOM before it has finished rendering, when to scope axe to ",[328,333,334],{},"document"," instead of ",[328,337,338],{},"container",", and which failures are jsdom artifacts you should stop chasing in jest-axe entirely. It builds on the patterns in ",[341,342,261],"a",{"href":343},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002F"," and feeds the pipeline rules covered in ",[341,346,297],{"href":347},"\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002F",".",[324,350,351,352,355,356,359],{},"The rules most often implicated are ",[328,353,354],{},"4.1.2 Name, Role, Value"," (a control lost its name during an async render) and ",[328,357,358],{},"1.3.1 Info and Relationships"," (a reference broke because the target mounted in a portal axe never scanned).",[361,362,364],"h2",{"id":363},"reading-the-violations-array","Reading the Violations Array",[324,366,367,368,371,372,375,376,379],{},"When ",[328,369,370],{},"toHaveNoViolations"," fails it prints a formatted summary, but the structured data behind it is what you debug from. Each entry in ",[328,373,374],{},"results.violations"," is a rule failure; each ",[328,377,378],{},"node"," inside it is a specific element that broke the rule.",[381,382,387],"pre",{"className":383,"code":384,"language":385,"meta":386,"style":386},"language-ts shiki shiki-themes github-light github-dark","const results = await axe(container);\n\nresults.violations.forEach((v) => {\n  console.log(v.id);             \u002F\u002F rule id, e.g. 'button-name'\n  console.log(v.impact);         \u002F\u002F 'minor' | 'moderate' | 'serious' | 'critical'\n  console.log(v.help);           \u002F\u002F one-line description of the rule\n  v.nodes.forEach((n) => {\n    console.log(n.target);       \u002F\u002F CSS selector path to the offending element\n    console.log(n.html);         \u002F\u002F the element's outer HTML at failure time\n    console.log(n.failureSummary); \u002F\u002F exactly what to fix, e.g. \"Fix any of the following...\"\n  });\n});\n","ts","",[328,388,389,416,423,448,464,477,490,509,523,536,549,555],{"__ignoreMap":386},[390,391,394,398,402,405,408,412],"span",{"class":392,"line":393},"line",1,[390,395,397],{"class":396},"szBVR","const",[390,399,401],{"class":400},"sj4cs"," results",[390,403,404],{"class":396}," =",[390,406,407],{"class":396}," await",[390,409,411],{"class":410},"sScJk"," axe",[390,413,415],{"class":414},"sVt8B","(container);\n",[390,417,419],{"class":392,"line":418},2,[390,420,422],{"emptyLinePlaceholder":421},true,"\n",[390,424,426,429,432,435,439,442,445],{"class":392,"line":425},3,[390,427,428],{"class":414},"results.violations.",[390,430,431],{"class":410},"forEach",[390,433,434],{"class":414},"((",[390,436,438],{"class":437},"s4XuR","v",[390,440,441],{"class":414},") ",[390,443,444],{"class":396},"=>",[390,446,447],{"class":414}," {\n",[390,449,451,454,457,460],{"class":392,"line":450},4,[390,452,453],{"class":414},"  console.",[390,455,456],{"class":410},"log",[390,458,459],{"class":414},"(v.id);             ",[390,461,463],{"class":462},"sJ8bj","\u002F\u002F rule id, e.g. 'button-name'\n",[390,465,467,469,471,474],{"class":392,"line":466},5,[390,468,453],{"class":414},[390,470,456],{"class":410},[390,472,473],{"class":414},"(v.impact);         ",[390,475,476],{"class":462},"\u002F\u002F 'minor' | 'moderate' | 'serious' | 'critical'\n",[390,478,480,482,484,487],{"class":392,"line":479},6,[390,481,453],{"class":414},[390,483,456],{"class":410},[390,485,486],{"class":414},"(v.help);           ",[390,488,489],{"class":462},"\u002F\u002F one-line description of the rule\n",[390,491,493,496,498,500,503,505,507],{"class":392,"line":492},7,[390,494,495],{"class":414},"  v.nodes.",[390,497,431],{"class":410},[390,499,434],{"class":414},[390,501,502],{"class":437},"n",[390,504,441],{"class":414},[390,506,444],{"class":396},[390,508,447],{"class":414},[390,510,512,515,517,520],{"class":392,"line":511},8,[390,513,514],{"class":414},"    console.",[390,516,456],{"class":410},[390,518,519],{"class":414},"(n.target);       ",[390,521,522],{"class":462},"\u002F\u002F CSS selector path to the offending element\n",[390,524,526,528,530,533],{"class":392,"line":525},9,[390,527,514],{"class":414},[390,529,456],{"class":410},[390,531,532],{"class":414},"(n.html);         ",[390,534,535],{"class":462},"\u002F\u002F the element's outer HTML at failure time\n",[390,537,539,541,543,546],{"class":392,"line":538},10,[390,540,514],{"class":414},[390,542,456],{"class":410},[390,544,545],{"class":414},"(n.failureSummary); ",[390,547,548],{"class":462},"\u002F\u002F exactly what to fix, e.g. \"Fix any of the following...\"\n",[390,550,552],{"class":392,"line":551},11,[390,553,554],{"class":414},"  });\n",[390,556,558],{"class":392,"line":557},12,[390,559,560],{"class":414},"});\n",[324,562,563,564,570,571,576,577,582,583,588,589,592,593,597],{},"The four fields that resolve most cases: ",[565,566,567],"strong",{},[328,568,569],{},"id"," tells you which rule (look it up in the axe-core ruleset), ",[565,572,573],{},[328,574,575],{},"impact"," tells you severity, ",[565,578,579],{},[328,580,581],{},"target"," is the selector that points at the element, and ",[565,584,585],{},[328,586,587],{},"failureSummary"," spells out the remediation. In CI, the printed ",[328,590,591],{},"html"," is your most valuable clue—it shows the DOM ",[594,595,596],"em",{},"as axe saw it",", which is frequently a half-rendered state your local timing skipped past. Log the full violation in CI rather than only the matcher's summary when a failure is hard to reproduce.",[599,600],"hr",{},[361,602,604],{"id":603},"async-rendering-wait-before-you-run-axe","Async Rendering: Wait Before You Run axe",[324,606,607,608,611],{},"The classic CI-only failure is a race. Your local machine renders the component fast enough that the DOM is settled by the time axe runs; CI runs slower (or faster, exposing a different order), and axe inspects an intermediate state—a button before its label loads, an input before its error mounts. The fix is to never call ",[328,609,610],{},"axe"," until the DOM you intend to test exists.",[381,613,617],{"className":614,"code":615,"language":616,"meta":386,"style":386},"language-tsx shiki shiki-themes github-light github-dark","import { render, screen, waitFor } from '@testing-library\u002Freact';\nimport { axe } from 'jest-axe';\n\ntest('runs axe only after async content settles', async () => {\n  const { container } = render(\u003CUserProfile id=\"42\" \u002F>);\n\n  \u002F\u002F BAD: axe here may inspect a loading skeleton with unnamed controls\n  \u002F\u002F expect(await axe(container)).toHaveNoViolations();\n\n  \u002F\u002F GOOD: wait for the real content to appear first\n  await screen.findByRole('heading', { name: \u002Fada lovelace\u002Fi });\n\n  \u002F\u002F Or wait on a stable post-load condition before scanning\n  await waitFor(() => expect(screen.queryByText(\u002Floading\u002Fi)).not.toBeInTheDocument());\n\n  expect(await axe(container)).toHaveNoViolations();\n});\n","tsx",[328,618,619,637,651,655,679,715,719,724,729,733,738,772,776,782,824,829,850],{"__ignoreMap":386},[390,620,621,624,627,630,634],{"class":392,"line":393},[390,622,623],{"class":396},"import",[390,625,626],{"class":414}," { render, screen, waitFor } ",[390,628,629],{"class":396},"from",[390,631,633],{"class":632},"sZZnC"," '@testing-library\u002Freact'",[390,635,636],{"class":414},";\n",[390,638,639,641,644,646,649],{"class":392,"line":418},[390,640,623],{"class":396},[390,642,643],{"class":414}," { axe } ",[390,645,629],{"class":396},[390,647,648],{"class":632}," 'jest-axe'",[390,650,636],{"class":414},[390,652,653],{"class":392,"line":425},[390,654,422],{"emptyLinePlaceholder":421},[390,656,657,660,663,666,669,672,675,677],{"class":392,"line":450},[390,658,659],{"class":410},"test",[390,661,662],{"class":414},"(",[390,664,665],{"class":632},"'runs axe only after async content settles'",[390,667,668],{"class":414},", ",[390,670,671],{"class":396},"async",[390,673,674],{"class":414}," () ",[390,676,444],{"class":396},[390,678,447],{"class":414},[390,680,681,684,687,689,692,695,698,701,704,707,709,712],{"class":392,"line":466},[390,682,683],{"class":396},"  const",[390,685,686],{"class":414}," { ",[390,688,338],{"class":400},[390,690,691],{"class":414}," } ",[390,693,694],{"class":396},"=",[390,696,697],{"class":410}," render",[390,699,700],{"class":414},"(\u003C",[390,702,703],{"class":400},"UserProfile",[390,705,706],{"class":410}," id",[390,708,694],{"class":396},[390,710,711],{"class":632},"\"42\"",[390,713,714],{"class":414}," \u002F>);\n",[390,716,717],{"class":392,"line":479},[390,718,422],{"emptyLinePlaceholder":421},[390,720,721],{"class":392,"line":492},[390,722,723],{"class":462},"  \u002F\u002F BAD: axe here may inspect a loading skeleton with unnamed controls\n",[390,725,726],{"class":392,"line":511},[390,727,728],{"class":462},"  \u002F\u002F expect(await axe(container)).toHaveNoViolations();\n",[390,730,731],{"class":392,"line":525},[390,732,422],{"emptyLinePlaceholder":421},[390,734,735],{"class":392,"line":538},[390,736,737],{"class":462},"  \u002F\u002F GOOD: wait for the real content to appear first\n",[390,739,740,743,746,749,751,754,757,760,764,766,769],{"class":392,"line":551},[390,741,742],{"class":396},"  await",[390,744,745],{"class":414}," screen.",[390,747,748],{"class":410},"findByRole",[390,750,662],{"class":414},[390,752,753],{"class":632},"'heading'",[390,755,756],{"class":414},", { name:",[390,758,759],{"class":632}," \u002F",[390,761,763],{"class":762},"sA_wV","ada lovelace",[390,765,86],{"class":632},[390,767,768],{"class":396},"i",[390,770,771],{"class":414}," });\n",[390,773,774],{"class":392,"line":557},[390,775,422],{"emptyLinePlaceholder":421},[390,777,779],{"class":392,"line":778},13,[390,780,781],{"class":462},"  \u002F\u002F Or wait on a stable post-load condition before scanning\n",[390,783,785,787,790,793,795,798,801,804,806,808,811,813,815,818,821],{"class":392,"line":784},14,[390,786,742],{"class":396},[390,788,789],{"class":410}," waitFor",[390,791,792],{"class":414},"(() ",[390,794,444],{"class":396},[390,796,797],{"class":410}," expect",[390,799,800],{"class":414},"(screen.",[390,802,803],{"class":410},"queryByText",[390,805,662],{"class":414},[390,807,86],{"class":632},[390,809,810],{"class":762},"loading",[390,812,86],{"class":632},[390,814,768],{"class":396},[390,816,817],{"class":414},")).not.",[390,819,820],{"class":410},"toBeInTheDocument",[390,822,823],{"class":414},"());\n",[390,825,827],{"class":392,"line":826},15,[390,828,422],{"emptyLinePlaceholder":421},[390,830,832,835,837,840,842,845,847],{"class":392,"line":831},16,[390,833,834],{"class":410},"  expect",[390,836,662],{"class":414},[390,838,839],{"class":396},"await",[390,841,411],{"class":410},[390,843,844],{"class":414},"(container)).",[390,846,370],{"class":410},[390,848,849],{"class":414},"();\n",[390,851,853],{"class":392,"line":852},17,[390,854,560],{"class":414},[324,856,857,860,861,864],{},[328,858,859],{},"findBy*"," queries retry until the element appears or time out; ",[328,862,863],{},"waitFor"," retries an arbitrary assertion. Use whichever expresses \"the DOM is ready.\" Running axe behind an explicit wait removes the entire class of order-dependent CI failures—if the component has finished rendering before axe runs, the result is deterministic across machines.",[599,866],{},[361,868,870],{"id":869},"portals-scope-to-document-not-container","Portals: Scope to document, Not container",[324,872,873,874,877,878,880,881,884],{},"React portals render outside the React subtree—typically to ",[328,875,876],{},"document.body",". Modals, tooltips, toasts, and popovers commonly use them. If you scope axe to the ",[328,879,338],{}," returned by ",[328,882,883],{},"render",", the portal content lives elsewhere in the DOM and axe never sees it. Locally you might not notice; in CI a portal-rendered violation surfaces only when something else changes scope.",[381,886,888],{"className":614,"code":887,"language":616,"meta":386,"style":386},"test('modal in a portal is scanned by axe', async () => {\n  const user = userEvent.setup();\n  render(\u003CDeleteDialog \u002F>); \u002F\u002F dialog portals to document.body\n\n  await user.click(screen.getByRole('button', { name: \u002Fdelete\u002Fi }));\n  await screen.findByRole('dialog');\n\n  \u002F\u002F container would MISS the portal—scan the whole document instead\n  expect(await axe(document.body)).toHaveNoViolations();\n});\n",[328,889,890,909,926,942,946,980,996,1000,1005,1022],{"__ignoreMap":386},[390,891,892,894,896,899,901,903,905,907],{"class":392,"line":393},[390,893,659],{"class":410},[390,895,662],{"class":414},[390,897,898],{"class":632},"'modal in a portal is scanned by axe'",[390,900,668],{"class":414},[390,902,671],{"class":396},[390,904,674],{"class":414},[390,906,444],{"class":396},[390,908,447],{"class":414},[390,910,911,913,916,918,921,924],{"class":392,"line":418},[390,912,683],{"class":396},[390,914,915],{"class":400}," user",[390,917,404],{"class":396},[390,919,920],{"class":414}," userEvent.",[390,922,923],{"class":410},"setup",[390,925,849],{"class":414},[390,927,928,931,933,936,939],{"class":392,"line":425},[390,929,930],{"class":410},"  render",[390,932,700],{"class":414},[390,934,935],{"class":400},"DeleteDialog",[390,937,938],{"class":414}," \u002F>); ",[390,940,941],{"class":462},"\u002F\u002F dialog portals to document.body\n",[390,943,944],{"class":392,"line":450},[390,945,422],{"emptyLinePlaceholder":421},[390,947,948,950,953,956,958,961,963,966,968,970,973,975,977],{"class":392,"line":466},[390,949,742],{"class":396},[390,951,952],{"class":414}," user.",[390,954,955],{"class":410},"click",[390,957,800],{"class":414},[390,959,960],{"class":410},"getByRole",[390,962,662],{"class":414},[390,964,965],{"class":632},"'button'",[390,967,756],{"class":414},[390,969,759],{"class":632},[390,971,972],{"class":762},"delete",[390,974,86],{"class":632},[390,976,768],{"class":396},[390,978,979],{"class":414}," }));\n",[390,981,982,984,986,988,990,993],{"class":392,"line":479},[390,983,742],{"class":396},[390,985,745],{"class":414},[390,987,748],{"class":410},[390,989,662],{"class":414},[390,991,992],{"class":632},"'dialog'",[390,994,995],{"class":414},");\n",[390,997,998],{"class":392,"line":492},[390,999,422],{"emptyLinePlaceholder":421},[390,1001,1002],{"class":392,"line":511},[390,1003,1004],{"class":462},"  \u002F\u002F container would MISS the portal—scan the whole document instead\n",[390,1006,1007,1009,1011,1013,1015,1018,1020],{"class":392,"line":525},[390,1008,834],{"class":410},[390,1010,662],{"class":414},[390,1012,839],{"class":396},[390,1014,411],{"class":410},[390,1016,1017],{"class":414},"(document.body)).",[390,1019,370],{"class":410},[390,1021,849],{"class":414},[390,1023,1024],{"class":392,"line":538},[390,1025,560],{"class":414},[324,1027,1028,1029,1031,1032,1034,1035,1038,1039,1041,1042,1044],{},"The asymmetry to remember: Testing Library queries like ",[328,1030,960],{}," search the entire ",[328,1033,334],{}," by default, so they find portal content fine—which is exactly why a portal can pass your role\u002Fname assertions while ",[328,1036,1037],{},"axe(container)"," silently scanned nothing. When a component portals, scope axe to ",[328,1040,876],{}," (or the portal root). When it does not, keep the tighter ",[328,1043,338],{}," scope so axe stays focused on the component under test rather than test scaffolding.",[599,1046],{},[361,1048,1050],{"id":1049},"flaky-timing-and-fake-timers","Flaky Timing and Fake Timers",[324,1052,1053,1054,1057],{},"Some CI flake comes from timers, not rendering: a toast that auto-dismisses on a ",[328,1055,1056],{},"setTimeout",", a debounced validation, an animation gate. If axe runs while a timer-driven element is mid-transition, the result is nondeterministic. Control time explicitly instead of hoping the wall clock cooperates.",[381,1059,1061],{"className":614,"code":1060,"language":616,"meta":386,"style":386},"test('scans the toast before its auto-dismiss timer fires', async () => {\n  jest.useFakeTimers();\n  const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });\n  render(\u003CSaveButton \u002F>);\n\n  await user.click(screen.getByRole('button', { name: \u002Fsave\u002Fi }));\n  await screen.findByRole('status'); \u002F\u002F toast is present\n\n  expect(await axe(document.body)).toHaveNoViolations();\n\n  jest.runOnlyPendingTimers(); \u002F\u002F let the dismiss timer fire deterministically\n  jest.useRealTimers();\n});\n",[328,1062,1063,1082,1092,1107,1118,1122,1151,1170,1174,1190,1194,1207,1216],{"__ignoreMap":386},[390,1064,1065,1067,1069,1072,1074,1076,1078,1080],{"class":392,"line":393},[390,1066,659],{"class":410},[390,1068,662],{"class":414},[390,1070,1071],{"class":632},"'scans the toast before its auto-dismiss timer fires'",[390,1073,668],{"class":414},[390,1075,671],{"class":396},[390,1077,674],{"class":414},[390,1079,444],{"class":396},[390,1081,447],{"class":414},[390,1083,1084,1087,1090],{"class":392,"line":418},[390,1085,1086],{"class":414},"  jest.",[390,1088,1089],{"class":410},"useFakeTimers",[390,1091,849],{"class":414},[390,1093,1094,1096,1098,1100,1102,1104],{"class":392,"line":425},[390,1095,683],{"class":396},[390,1097,915],{"class":400},[390,1099,404],{"class":396},[390,1101,920],{"class":414},[390,1103,923],{"class":410},[390,1105,1106],{"class":414},"({ advanceTimers: jest.advanceTimersByTime });\n",[390,1108,1109,1111,1113,1116],{"class":392,"line":450},[390,1110,930],{"class":410},[390,1112,700],{"class":414},[390,1114,1115],{"class":400},"SaveButton",[390,1117,714],{"class":414},[390,1119,1120],{"class":392,"line":466},[390,1121,422],{"emptyLinePlaceholder":421},[390,1123,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1145,1147,1149],{"class":392,"line":479},[390,1125,742],{"class":396},[390,1127,952],{"class":414},[390,1129,955],{"class":410},[390,1131,800],{"class":414},[390,1133,960],{"class":410},[390,1135,662],{"class":414},[390,1137,965],{"class":632},[390,1139,756],{"class":414},[390,1141,759],{"class":632},[390,1143,1144],{"class":762},"save",[390,1146,86],{"class":632},[390,1148,768],{"class":396},[390,1150,979],{"class":414},[390,1152,1153,1155,1157,1159,1161,1164,1167],{"class":392,"line":492},[390,1154,742],{"class":396},[390,1156,745],{"class":414},[390,1158,748],{"class":410},[390,1160,662],{"class":414},[390,1162,1163],{"class":632},"'status'",[390,1165,1166],{"class":414},"); ",[390,1168,1169],{"class":462},"\u002F\u002F toast is present\n",[390,1171,1172],{"class":392,"line":511},[390,1173,422],{"emptyLinePlaceholder":421},[390,1175,1176,1178,1180,1182,1184,1186,1188],{"class":392,"line":525},[390,1177,834],{"class":410},[390,1179,662],{"class":414},[390,1181,839],{"class":396},[390,1183,411],{"class":410},[390,1185,1017],{"class":414},[390,1187,370],{"class":410},[390,1189,849],{"class":414},[390,1191,1192],{"class":392,"line":538},[390,1193,422],{"emptyLinePlaceholder":421},[390,1195,1196,1198,1201,1204],{"class":392,"line":551},[390,1197,1086],{"class":414},[390,1199,1200],{"class":410},"runOnlyPendingTimers",[390,1202,1203],{"class":414},"(); ",[390,1205,1206],{"class":462},"\u002F\u002F let the dismiss timer fire deterministically\n",[390,1208,1209,1211,1214],{"class":392,"line":557},[390,1210,1086],{"class":414},[390,1212,1213],{"class":410},"useRealTimers",[390,1215,849],{"class":414},[390,1217,1218],{"class":392,"line":778},[390,1219,560],{"class":414},[324,1221,1222,1223,1226],{},"When you adopt fake timers, wire ",[328,1224,1225],{},"userEvent.setup({ advanceTimers })"," so user interactions still flush microtasks—otherwise queries hang. The principle is the same as the async section: make the DOM state deterministic before axe inspects it, and CI stops disagreeing with your laptop.",[599,1228],{},[361,1230,1232],{"id":1231},"what-jsdom-simply-cannot-test","What jsdom Simply Cannot Test",[324,1234,1235],{},"Some \"CI failures\" are not failures—they are jsdom limitations producing noise. jsdom has no layout or paint pipeline, so any rule needing computed geometry or color cannot run meaningfully:",[1237,1238,1239,1252,1263],"ul",{},[1240,1241,1242,1247,1248,1251],"li",{},[565,1243,1244],{},[328,1245,1246],{},"color-contrast"," (",[328,1249,1250],{},"1.4.3 Contrast (Minimum)",") — no computed colors; disable it in jest-axe config and verify in a browser.",[1240,1253,1254,1247,1259,1262],{},[565,1255,1256],{},[328,1257,1258],{},"target-size",[328,1260,1261],{},"2.5.8 Target Size (Minimum)",") — no real pixel dimensions exist.",[1240,1264,1265,1268],{},[565,1266,1267],{},"Visibility\u002Focclusion rules"," — geometry is effectively zero, so \"is this visible\" is unanswerable.",[324,1270,1271],{},"Disable these explicitly so they never produce a misleading result:",[381,1273,1275],{"className":614,"code":1274,"language":616,"meta":386,"style":386},"const results = await axe(container, {\n  rules: { 'color-contrast': { enabled: false } }, \u002F\u002F jsdom can't compute it—own it in the browser\n});\n",[328,1276,1277,1292,1312],{"__ignoreMap":386},[390,1278,1279,1281,1283,1285,1287,1289],{"class":392,"line":393},[390,1280,397],{"class":396},[390,1282,401],{"class":400},[390,1284,404],{"class":396},[390,1286,407],{"class":396},[390,1288,411],{"class":410},[390,1290,1291],{"class":414},"(container, {\n",[390,1293,1294,1297,1300,1303,1306,1309],{"class":392,"line":418},[390,1295,1296],{"class":414},"  rules: { ",[390,1298,1299],{"class":632},"'color-contrast'",[390,1301,1302],{"class":414},": { enabled: ",[390,1304,1305],{"class":400},"false",[390,1307,1308],{"class":414}," } }, ",[390,1310,1311],{"class":462},"\u002F\u002F jsdom can't compute it—own it in the browser\n",[390,1313,1314],{"class":392,"line":425},[390,1315,560],{"class":414},[324,1317,1318,1319,348],{},"Chasing these in jest-axe wastes triage time. Move them to a real-browser run and gate them there. The pipeline-level strategy for splitting structural component checks from rendered checks lives in ",[341,1320,297],{"href":347},[599,1322],{},[361,1324,1326],{"id":1325},"how-to-verify","How to Verify",[324,1328,1329],{},"Reproduce the CI condition locally before declaring a fix. Force the slower, deterministic path and run the exact command CI runs:",[381,1331,1335],{"className":1332,"code":1333,"language":1334,"meta":386,"style":386},"language-bash shiki shiki-themes github-light github-dark","CI=true npx jest --runInBand --ci\n","bash",[328,1336,1337],{"__ignoreMap":386},[390,1338,1339,1342,1344,1347,1350,1353,1356],{"class":392,"line":393},[390,1340,1341],{"class":414},"CI",[390,1343,694],{"class":396},[390,1345,1346],{"class":632},"true",[390,1348,1349],{"class":410}," npx",[390,1351,1352],{"class":632}," jest",[390,1354,1355],{"class":400}," --runInBand",[390,1357,1358],{"class":400}," --ci\n",[324,1360,1361,1364,1365,1368,1369,668,1371,668,1373,668,1375,668,1377,1379,1380,1382],{},[328,1362,1363],{},"--runInBand"," removes worker parallelism (a frequent source of order-dependent flake), and ",[328,1366,1367],{},"--ci"," makes Jest treat the run like the pipeline does. If a test still passes locally but fails in CI, log the full violation—",[328,1370,569],{},[328,1372,575],{},[328,1374,581],{},[328,1376,591],{},[328,1378,587],{},"—from the CI run and read the ",[328,1381,591],{}," to see the DOM state axe captured; it usually reveals an async or portal scope gap. Confirm the fix by re-running the same command several times: a genuinely deterministic test passes every time. Finally, for any rule you disabled as a jsdom limitation, verify it in a real browser and add a manual NVDA\u002FVoiceOver spot-check so the coverage you removed from jest-axe is not simply lost.",[599,1384],{},[361,1386,1388],{"id":1387},"common-a11y-mistakes","Common a11y Mistakes",[1237,1390,1391,1403,1413,1422,1431],{},[1240,1392,1393,1399,1400,1402],{},[565,1394,1395,1396,1398],{},"Scoping ",[328,1397,1037],{}," for portal content"," — axe scans nothing while your role queries still pass, hiding the violation. Scope to ",[328,1401,876],{}," for portals.",[1240,1404,1405,1408,1409,86,1411,348],{},[565,1406,1407],{},"Running axe before async DOM settles"," — produces order-dependent CI failures. Gate axe behind ",[328,1410,859],{},[328,1412,863],{},[1240,1414,1415,1421],{},[565,1416,1417,1418,1420],{},"Leaving ",[328,1419,1246],{}," enabled in jsdom"," — yields misleading results; disable it and verify in a browser.",[1240,1423,1424,1427,1428,1430],{},[565,1425,1426],{},"Parallel workers masking flake"," — debug with ",[328,1429,1363],{}," so failures are reproducible.",[1240,1432,1433,1436,1437,1439,1440,668,1442,668,1444,1446],{},[565,1434,1435],{},"Reading only the matcher summary"," — the structured ",[328,1438,330],{}," array (",[328,1441,581],{},[328,1443,591],{},[328,1445,587],{},") tells you exactly what and where.",[599,1448],{},[361,1450,1452],{"id":1451},"conclusion","Conclusion",[324,1454,1455],{},"CI-only jest-axe failures decompose into a short checklist: did axe run after the DOM settled, did it scope to the place the content actually rendered, were timers deterministic, and is the failing rule something jsdom can even evaluate. Read the violation nodes, fix the scope and timing, push layout and color to the browser, and your component suite becomes a reliable gate instead of an intermittent annoyance.",[599,1457],{},[361,1459,1461],{"id":1460},"frequently-asked-questions","Frequently Asked Questions",[324,1463,1464,1467,1468,86,1470,1472,1473,1475,1476,1478],{},[565,1465,1466],{},"Why does my jest-axe test fail in CI but pass locally?","\nAlmost always timing or scope. CI runs at a different speed, so axe may inspect a half-rendered DOM your local run skipped past, or worker parallelism exposes an order your laptop didn't. Gate axe behind ",[328,1469,859],{},[328,1471,863],{},", run with ",[328,1474,1363],{}," to reproduce, and read the violation's ",[328,1477,591],{}," to see the state axe captured.",[324,1480,1481,1487,1488,1490,1491,1493,1494,1496,1497,1499],{},[565,1482,1483,1484,1486],{},"My modal passes ",[328,1485,960],{}," but axe reports nothing—why?","\nThe modal renders into a portal on ",[328,1489,876],{},". Testing Library queries search the whole document, so ",[328,1492,960],{}," finds it, but ",[328,1495,1037],{}," only scans the render container and misses the portal entirely. Scope axe to ",[328,1498,876],{}," when a component portals.",[324,1501,1502,1505,1506,1508,1509,1511,1512,1514,1515,1517,1518,1520,1521,1523],{},[565,1503,1504],{},"Which violation fields should I log to debug CI failures?","\nLog ",[328,1507,569],{}," (the rule), ",[328,1510,575],{}," (severity), ",[328,1513,581],{}," (selector to the element), ",[328,1516,591],{}," (the DOM as axe saw it), and ",[328,1519,587],{}," (the remediation). The ",[328,1522,591],{}," field is the most useful for CI-only failures because it reveals the exact intermediate state that triggered the rule.",[361,1525,1527,1530,1531,1533,1534,1536],{"id":1526},"should-i-just-disable-rules-that-fail-flakilyonly-if-the-rule-cannot-run-in-jsdom-like-color-contrast-or-target-size-and-then-verify-it-in-a-real-browser-instead-if-a-structural-rule-flakes-the-cause-is-async-or-scopefix-that-disabling-a-structural-rule-to-silence-flake-removes-real-coverage",[565,1528,1529],{},"Should I just disable rules that fail flakily?","\nOnly if the rule cannot run in jsdom (like ",[328,1532,1246],{}," or ",[328,1535,1258],{},"), and then verify it in a real browser instead. If a structural rule flakes, the cause is async or scope—fix that. Disabling a structural rule to silence flake removes real coverage.",[361,1538,1540],{"id":1539},"related-guides","Related guides",[1237,1542,1543,1547,1552],{},[1240,1544,1545],{},[341,1546,261],{"href":343},[1240,1548,1549],{},[341,1550,273],{"href":1551},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe\u002F",[1240,1553,1554],{},[341,1555,297],{"href":347},[1557,1558,1559],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":386,"searchDepth":418,"depth":418,"links":1561},[1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1573],{"id":363,"depth":418,"text":364},{"id":603,"depth":418,"text":604},{"id":869,"depth":418,"text":870},{"id":1049,"depth":418,"text":1050},{"id":1231,"depth":418,"text":1232},{"id":1325,"depth":418,"text":1326},{"id":1387,"depth":418,"text":1388},{"id":1451,"depth":418,"text":1452},{"id":1460,"depth":418,"text":1461},{"id":1526,"depth":418,"text":1572},"Should I just disable rules that fail flakily?\nOnly if the rule cannot run in jsdom (like color-contrast or target-size), and then verify it in a real browser instead. If a structural rule flakes, the cause is async or scope—fix that. Disabling a structural rule to silence flake removes real coverage.",{"id":1539,"depth":418,"text":1540},null,"Fix and triage jest-axe failures that only appear in CI—reading violation nodes, handling async\u002Fportal rendering, container vs document scope, and jsdom's limits.","md",{},false,{"title":267,"description":1575},"b18zl1VrqbsLCajvaXfNI0W4_FIflhFX_zzBOoqrKsg",[1582,1621,1622,1685],{"title":5,"path":6,"stem":7,"children":1583},[1584,1585,1588,1591,1597,1603,1612,1618],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":1586},[1587],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":1589},[1590],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":1592},[1593,1594],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":1595},[1596],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":1598},[1599,1600],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":1601},[1602],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":1604},[1605,1606,1609],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":1607},[1608],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":1610},[1611],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":1613},[1614,1615],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":1616},[1617],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":1619},[1620],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":1623},[1624,1625,1631,1643,1655,1658,1667,1679],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":1626},[1627,1628],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":1629},[1630],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":1632},[1633,1634,1637,1640],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":1635},[1636],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":1638},[1639],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":1641},[1642],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":1644},[1645,1646,1649,1652],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":1647},[1648],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":1650},[1651],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":1653},[1654],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":1656},[1657],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":1659},[1660,1661,1664],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":1662},[1663],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":1665},[1666],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":1668},[1669,1670,1673,1676],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":1671},[1672],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":1674},[1675],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":1677},[1678],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":1680},[1681,1682],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":1683},[1684],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":1686},[1687,1688,1697,1706,1715,1724],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":1689},[1690,1691,1694],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":1692},[1693],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":1695},[1696],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":1698},[1699,1700,1703],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":1701},[1702],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":1704},[1705],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":1707},[1708,1709,1712],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":1710},[1711],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":1713},[1714],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":1716},[1717,1718,1721],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":1719},[1720],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":1722},[1723],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":1725},[1726,1727,1730],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":1728},[1729],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":1731},[1732],{"title":309,"path":310,"stem":311},1781785524237]