[{"data":1,"prerenderedAt":2554},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002F":314,"content-navigation":2402},[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":279,"body":316,"date":2395,"description":2396,"extension":2397,"image":2395,"meta":2398,"modifiedAt":2395,"navigation":694,"noindex":2399,"path":280,"publishedAt":2395,"seo":2400,"stem":281,"updatedAt":2395,"__hash__":2401},"content\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Findex.md",{"type":317,"value":318,"toc":2384},"minimark",[319,323,350,356,385,390,408,411,416,419,424,469,477,592,602,604,608,614,619,642,894,901,1089,1097,1102,1104,1108,1131,1135,1156,1383,1390,1398,1400,1404,1413,1417,1432,1617,1628,1637,1639,1643,1650,1654,1676,1946,1953,1966,1968,1972,1982,1986,2000,2199,2210,2217,2219,2223,2284,2286,2290,2299,2314,2327,2336,2342,2344,2348,2380],[320,321,279],"h1",{"id":322},"end-to-end-accessibility-testing-with-playwright",[324,325,326,327,334,335,339,340,344,345,349],"p",{},"End-to-end accessibility testing exercises your application in a real browser, where layout, computed styles, focus order, and route transitions behave exactly as they do for users. This guide pairs ",[328,329,333],"a",{"href":330,"rel":331},"https:\u002F\u002Fplaywright.dev\u002F",[332],"nofollow","Playwright"," with ",[336,337,338],"code",{},"@axe-core\u002Fplaywright"," to move beyond static markup assertions and validate the ",[341,342,343],"em",{},"behavior"," that assistive technology depends on. It extends the broader strategy outlined in ",[328,346,348],{"href":347},"\u002Ftesting-and-automating-accessibility\u002F","Testing and Automating Accessibility"," by covering the layer that unit-level tools structurally cannot reach: keyboard-driven flows, dynamic announcements, and focus restoration across navigation.",[324,351,352],{},[353,354,355],"strong",{},"WCAG Success Criteria Addressed:",[357,358,359,365,370,375,380],"ul",{},[360,361,362],"li",{},[336,363,364],{},"2.1.1 Keyboard",[360,366,367],{},[336,368,369],{},"2.4.3 Focus Order",[360,371,372],{},[336,373,374],{},"2.4.7 Focus Visible",[360,376,377],{},[336,378,379],{},"4.1.3 Status Messages",[360,381,382],{},[336,383,384],{},"1.4.3 Contrast (Minimum)",[324,386,387],{},[353,388,389],{},"Core Testing Principles:",[357,391,392,395,398,405],{},[360,393,394],{},"Scan rendered pages with Axe to catch violations against the live accessibility tree.",[360,396,397],{},"Drive interactions with the keyboard, never synthetic clicks, to prove keyboard operability.",[360,399,400,401,404],{},"Assert focus state directly with ",[336,402,403],{},"toBeFocused()"," rather than inferring it.",[360,406,407],{},"Read live-region text after an action to confirm announcements actually reach the DOM.",[409,410],"hr",{},[412,413,415],"h2",{"id":414},"why-end-to-end-catches-what-jsdom-and-jest-axe-cannot","Why End-to-End Catches What jsdom and jest-axe Cannot",[324,417,418],{},"Unit-level accessibility tooling runs in jsdom, a simulated DOM with no rendering engine. That gap is not academic—it silently hides entire categories of violations that only surface in a real browser.",[324,420,421],{},[353,422,423],{},"What jsdom structurally cannot evaluate:",[357,425,426,439,449,463],{},[360,427,428,431,432,434,435,438],{},[353,429,430],{},"Color contrast."," jsdom does not compute styles from a layout engine, so ",[336,433,384],{}," checks are unreliable or skipped. Axe in a real browser reads the ",[341,436,437],{},"computed"," foreground and background colors.",[360,440,441,444,445,448],{},[353,442,443],{},"Real layout and visibility."," Elements hidden by ",[336,446,447],{},"overflow",", zero-size containers, off-screen positioning, or stacking context are only resolvable when the page is actually laid out.",[360,450,451,454,455,458,459,462],{},[353,452,453],{},"Native focus behavior."," jsdom approximates ",[336,456,457],{},"document.activeElement",", but tab order, scroll-into-view on focus, and ",[336,460,461],{},":focus-visible"," are browser concerns.",[360,464,465,468],{},[353,466,467],{},"Route transitions."," Single-page-app navigation, where the framework swaps the view and must move focus, plays out over real microtasks and animation frames that jsdom does not faithfully model.",[324,470,471,472,476],{},"This is complementary to, not a replacement for, ",[328,473,475],{"href":474},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002F","component testing with jest-axe",". Component tests give you fast, isolated feedback on a single unit. End-to-end tests verify the assembled application as a user experiences it. The diagram below shows the canonical shape of an end-to-end accessibility test: a single browser session that scans, then drives the keyboard, then asserts on the resulting focus and announcement state.",[478,479,486,487,486,491,486,495,486,521,486,556,486,579],"svg",{"role":480,"ariaLabelledBy":481,"viewBox":484,"style":485},"img",[482,483],"e2e-title","e2e-desc","0 0 760 240","width:100%;height:auto;max-width:760px","\n  ",[488,489,490],"title",{"id":482},"End-to-end accessibility test flow in Playwright",[492,493,494],"desc",{"id":483},"A left-to-right sequence: navigate to the page, run an Axe scan and assert zero violations, drive a keyboard-only flow, then assert focus position and read the live-region text.",[496,497,502,503,502,511,502,515,502,518,486],"g",{"style":498,"fill":499,"stroke":500,"color":501},"stroke-width:2","var(--surface)","currentColor","var(--primary)","\n    ",[504,505],"rect",{"x":506,"y":507,"width":508,"height":509,"rx":510},"12","92","150","56","8",[504,512],{"x":513,"y":507,"width":508,"height":509,"rx":510,"fill":514},"198","var(--primary-soft)",[504,516],{"x":517,"y":507,"width":508,"height":509,"rx":510},"384",[504,519],{"x":520,"y":507,"width":508,"height":509,"rx":510,"fill":514},"570",[496,522,502,525,502,531,502,535,502,539,502,542,502,546,502,549,502,553,486],{"style":523,"fill":524},"font-size:14px;text-anchor:middle","var(--text)",[526,527,530],"text",{"x":528,"y":529},"87","116","page.goto()",[526,532,534],{"x":528,"y":533},"136","navigate",[526,536,538],{"x":537,"y":529},"273","AxeBuilder",[526,540,541],{"x":537,"y":533},".analyze()",[526,543,545],{"x":544,"y":529},"459","keyboard flow",[526,547,548],{"x":544,"y":533},"Tab \u002F Enter \u002F Esc",[526,550,552],{"x":551,"y":529},"645","assert focus",[526,554,555],{"x":551,"y":533},"+ live region",[496,557,502,560,502,564,502,567,502,570,502,573,502,576,486],{"style":498,"stroke":500,"color":558,"fill":559},"var(--primary-strong)","none",[561,562],"path",{"d":563},"M162 120 L196 120",[561,565],{"d":566},"M348 120 L382 120",[561,568],{"d":569},"M534 120 L568 120",[561,571],{"d":572,"fill":558},"M190 116 l8 4 -8 4z",[561,574],{"d":575,"fill":558},"M376 116 l8 4 -8 4z",[561,577],{"d":578,"fill":558},"M562 116 l8 4 -8 4z",[496,580,502,583,502,588,486],{"style":581,"fill":582},"font-size:13px;text-anchor:middle","var(--muted)",[526,584,587],{"x":585,"y":586},"380","44","One browser session, real layout, real focus",[526,589,591],{"x":585,"y":590},"208","Each arrow is an awaited step; assertions fail the test loudly",[324,593,594,597,598,601],{},[353,595,596],{},"How to verify:"," Run the same suite once with the ",[336,599,600],{},"chromium"," project and confirm contrast violations appear that your jsdom-based suite never reported. Manually spot-check one flagged element in DevTools to confirm the computed contrast ratio matches Axe's report.",[409,603],{},[412,605,607],{"id":606},"scanning-pages-with-axebuilder-and-asserting-zero-violations","Scanning Pages with AxeBuilder and Asserting Zero Violations",[324,609,610,611,613],{},"The foundation of an end-to-end accessibility suite is a full-page scan against the live accessibility tree. ",[336,612,338],{}," injects Axe into the page under test and returns structured results you can assert against.",[324,615,616],{},[353,617,618],{},"Implementation Guidelines:",[357,620,621,628,639],{},[360,622,623,624,627],{},"Scan ",[341,625,626],{},"after"," the page has settled—wait for a known element or network idle so dynamic content is present.",[360,629,630,631,634,635,638],{},"Tag scans to the standard you target (for example ",[336,632,633],{},"wcag2a",", ",[336,636,637],{},"wcag2aa",") to keep results aligned with your compliance baseline.",[360,640,641],{},"Attach violation details to the test report so failures are actionable, not just red.",[643,644,649],"pre",{"className":645,"code":646,"language":647,"meta":648,"style":648},"language-ts shiki shiki-themes github-light github-dark","import { test, expect } from '@playwright\u002Ftest';\nimport AxeBuilder from '@axe-core\u002Fplaywright';\n\ntest('checkout page has no detectable WCAG A\u002FAA violations', async ({ page }) => {\n  await page.goto('\u002Fcheckout');\n  \u002F\u002F Wait for the real, settled DOM so Axe scans rendered content, not a skeleton.\n  await page.getByRole('heading', { name: 'Checkout' }).waitFor();\n\n  const results = await new AxeBuilder({ page })\n    \u002F\u002F Constrain the scan to the standards your compliance baseline targets.\n    .withTags(['wcag2a', 'wcag2aa'])\n    .analyze();\n\n  \u002F\u002F A11y rationale: zero violations against the live accessibility tree,\n  \u002F\u002F including contrast computed from real styles that jsdom cannot evaluate.\n  expect(results.violations).toEqual([]);\n});\n","ts","",[336,650,651,674,689,696,730,750,757,787,792,817,823,846,856,861,867,873,888],{"__ignoreMap":648},[652,653,656,660,664,667,671],"span",{"class":654,"line":655},"line",1,[652,657,659],{"class":658},"szBVR","import",[652,661,663],{"class":662},"sVt8B"," { test, expect } ",[652,665,666],{"class":658},"from",[652,668,670],{"class":669},"sZZnC"," '@playwright\u002Ftest'",[652,672,673],{"class":662},";\n",[652,675,677,679,682,684,687],{"class":654,"line":676},2,[652,678,659],{"class":658},[652,680,681],{"class":662}," AxeBuilder ",[652,683,666],{"class":658},[652,685,686],{"class":669}," '@axe-core\u002Fplaywright'",[652,688,673],{"class":662},[652,690,692],{"class":654,"line":691},3,[652,693,695],{"emptyLinePlaceholder":694},true,"\n",[652,697,699,703,706,709,711,714,717,721,724,727],{"class":654,"line":698},4,[652,700,702],{"class":701},"sScJk","test",[652,704,705],{"class":662},"(",[652,707,708],{"class":669},"'checkout page has no detectable WCAG A\u002FAA violations'",[652,710,634],{"class":662},[652,712,713],{"class":658},"async",[652,715,716],{"class":662}," ({ ",[652,718,720],{"class":719},"s4XuR","page",[652,722,723],{"class":662}," }) ",[652,725,726],{"class":658},"=>",[652,728,729],{"class":662}," {\n",[652,731,733,736,739,742,744,747],{"class":654,"line":732},5,[652,734,735],{"class":658},"  await",[652,737,738],{"class":662}," page.",[652,740,741],{"class":701},"goto",[652,743,705],{"class":662},[652,745,746],{"class":669},"'\u002Fcheckout'",[652,748,749],{"class":662},");\n",[652,751,753],{"class":654,"line":752},6,[652,754,756],{"class":755},"sJ8bj","  \u002F\u002F Wait for the real, settled DOM so Axe scans rendered content, not a skeleton.\n",[652,758,760,762,764,767,769,772,775,778,781,784],{"class":654,"line":759},7,[652,761,735],{"class":658},[652,763,738],{"class":662},[652,765,766],{"class":701},"getByRole",[652,768,705],{"class":662},[652,770,771],{"class":669},"'heading'",[652,773,774],{"class":662},", { name: ",[652,776,777],{"class":669},"'Checkout'",[652,779,780],{"class":662}," }).",[652,782,783],{"class":701},"waitFor",[652,785,786],{"class":662},"();\n",[652,788,790],{"class":654,"line":789},8,[652,791,695],{"emptyLinePlaceholder":694},[652,793,795,798,802,805,808,811,814],{"class":654,"line":794},9,[652,796,797],{"class":658},"  const",[652,799,801],{"class":800},"sj4cs"," results",[652,803,804],{"class":658}," =",[652,806,807],{"class":658}," await",[652,809,810],{"class":658}," new",[652,812,813],{"class":701}," AxeBuilder",[652,815,816],{"class":662},"({ page })\n",[652,818,820],{"class":654,"line":819},10,[652,821,822],{"class":755},"    \u002F\u002F Constrain the scan to the standards your compliance baseline targets.\n",[652,824,826,829,832,835,838,840,843],{"class":654,"line":825},11,[652,827,828],{"class":662},"    .",[652,830,831],{"class":701},"withTags",[652,833,834],{"class":662},"([",[652,836,837],{"class":669},"'wcag2a'",[652,839,634],{"class":662},[652,841,842],{"class":669},"'wcag2aa'",[652,844,845],{"class":662},"])\n",[652,847,849,851,854],{"class":654,"line":848},12,[652,850,828],{"class":662},[652,852,853],{"class":701},"analyze",[652,855,786],{"class":662},[652,857,859],{"class":654,"line":858},13,[652,860,695],{"emptyLinePlaceholder":694},[652,862,864],{"class":654,"line":863},14,[652,865,866],{"class":755},"  \u002F\u002F A11y rationale: zero violations against the live accessibility tree,\n",[652,868,870],{"class":654,"line":869},15,[652,871,872],{"class":755},"  \u002F\u002F including contrast computed from real styles that jsdom cannot evaluate.\n",[652,874,876,879,882,885],{"class":654,"line":875},16,[652,877,878],{"class":701},"  expect",[652,880,881],{"class":662},"(results.violations).",[652,883,884],{"class":701},"toEqual",[652,886,887],{"class":662},"([]);\n",[652,889,891],{"class":654,"line":890},17,[652,892,893],{"class":662},"});\n",[324,895,896,897,900],{},"When a scan fails, surface the offending nodes rather than a bare count. This keeps the signal high and prevents engineers from re-running locally just to learn ",[341,898,899],{},"what"," broke.",[643,902,904],{"className":645,"code":903,"language":647,"meta":648,"style":648},"test('product grid is accessible', async ({ page }, testInfo) => {\n  await page.goto('\u002Fproducts');\n  await page.getByRole('list', { name: 'Products' }).waitFor();\n\n  const results = await new AxeBuilder({ page }).analyze();\n\n  \u002F\u002F Attach full violation data to the HTML report for triage.\n  await testInfo.attach('axe-results', {\n    body: JSON.stringify(results.violations, null, 2),\n    contentType: 'application\u002Fjson',\n  });\n\n  expect(results.violations).toEqual([]);\n});\n",[336,905,906,936,951,975,979,1000,1004,1009,1027,1055,1066,1071,1075,1085],{"__ignoreMap":648},[652,907,908,910,912,915,917,919,921,923,926,929,932,934],{"class":654,"line":655},[652,909,702],{"class":701},[652,911,705],{"class":662},[652,913,914],{"class":669},"'product grid is accessible'",[652,916,634],{"class":662},[652,918,713],{"class":658},[652,920,716],{"class":662},[652,922,720],{"class":719},[652,924,925],{"class":662}," }, ",[652,927,928],{"class":719},"testInfo",[652,930,931],{"class":662},") ",[652,933,726],{"class":658},[652,935,729],{"class":662},[652,937,938,940,942,944,946,949],{"class":654,"line":676},[652,939,735],{"class":658},[652,941,738],{"class":662},[652,943,741],{"class":701},[652,945,705],{"class":662},[652,947,948],{"class":669},"'\u002Fproducts'",[652,950,749],{"class":662},[652,952,953,955,957,959,961,964,966,969,971,973],{"class":654,"line":691},[652,954,735],{"class":658},[652,956,738],{"class":662},[652,958,766],{"class":701},[652,960,705],{"class":662},[652,962,963],{"class":669},"'list'",[652,965,774],{"class":662},[652,967,968],{"class":669},"'Products'",[652,970,780],{"class":662},[652,972,783],{"class":701},[652,974,786],{"class":662},[652,976,977],{"class":654,"line":698},[652,978,695],{"emptyLinePlaceholder":694},[652,980,981,983,985,987,989,991,993,996,998],{"class":654,"line":732},[652,982,797],{"class":658},[652,984,801],{"class":800},[652,986,804],{"class":658},[652,988,807],{"class":658},[652,990,810],{"class":658},[652,992,813],{"class":701},[652,994,995],{"class":662},"({ page }).",[652,997,853],{"class":701},[652,999,786],{"class":662},[652,1001,1002],{"class":654,"line":752},[652,1003,695],{"emptyLinePlaceholder":694},[652,1005,1006],{"class":654,"line":759},[652,1007,1008],{"class":755},"  \u002F\u002F Attach full violation data to the HTML report for triage.\n",[652,1010,1011,1013,1016,1019,1021,1024],{"class":654,"line":789},[652,1012,735],{"class":658},[652,1014,1015],{"class":662}," testInfo.",[652,1017,1018],{"class":701},"attach",[652,1020,705],{"class":662},[652,1022,1023],{"class":669},"'axe-results'",[652,1025,1026],{"class":662},", {\n",[652,1028,1029,1032,1035,1038,1041,1044,1047,1049,1052],{"class":654,"line":794},[652,1030,1031],{"class":662},"    body: ",[652,1033,1034],{"class":800},"JSON",[652,1036,1037],{"class":662},".",[652,1039,1040],{"class":701},"stringify",[652,1042,1043],{"class":662},"(results.violations, ",[652,1045,1046],{"class":800},"null",[652,1048,634],{"class":662},[652,1050,1051],{"class":800},"2",[652,1053,1054],{"class":662},"),\n",[652,1056,1057,1060,1063],{"class":654,"line":819},[652,1058,1059],{"class":662},"    contentType: ",[652,1061,1062],{"class":669},"'application\u002Fjson'",[652,1064,1065],{"class":662},",\n",[652,1067,1068],{"class":654,"line":825},[652,1069,1070],{"class":662},"  });\n",[652,1072,1073],{"class":654,"line":848},[652,1074,695],{"emptyLinePlaceholder":694},[652,1076,1077,1079,1081,1083],{"class":654,"line":858},[652,1078,878],{"class":701},[652,1080,881],{"class":662},[652,1082,884],{"class":701},[652,1084,887],{"class":662},[652,1086,1087],{"class":654,"line":863},[652,1088,893],{"class":662},[324,1090,1091,1092,1096],{},"For audits that prioritize remediation by severity or that combine performance and best-practice signals, pair these scans with ",[328,1093,1095],{"href":1094},"\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002F","accessibility audits with Lighthouse",". Axe gives you deterministic, gateable rule checks; Lighthouse gives you a broader scored snapshot.",[324,1098,1099,1101],{},[353,1100,596],{}," Introduce a deliberate violation (remove a button's accessible name) and confirm the test fails with that rule ID in the attachment. Then revert and confirm a clean pass.",[409,1103],{},[412,1105,1107],{"id":1106},"driving-keyboard-only-flows","Driving Keyboard-Only Flows",[324,1109,1110,1111,1114,1115,1117,1118,634,1121,634,1124,1127,1128,1037],{},"Synthetic clicks (",[336,1112,1113],{},"locator.click()",") bypass the keyboard entirely, so a test built on them can pass while the UI is completely unusable without a mouse. To validate ",[336,1116,364],{},", you must move through the interface using only ",[336,1119,1120],{},"Tab",[336,1122,1123],{},"Enter",[336,1125,1126],{},"Space",", and ",[336,1129,1130],{},"Escape",[324,1132,1133],{},[353,1134,618],{},[357,1136,1137,1144,1153],{},[360,1138,1139,1140,1143],{},"Use ",[336,1141,1142],{},"page.keyboard.press('Tab')"," to advance focus and assert on each landing element.",[360,1145,1146,1147,1149,1150,1152],{},"Activate controls with ",[336,1148,1123],{}," or ",[336,1151,1126],{},"—the same keys a real keyboard user presses.",[360,1154,1155],{},"Treat any control you cannot reach or activate from the keyboard as a hard failure.",[643,1157,1159],{"className":645,"code":1158,"language":647,"meta":648,"style":648},"test('primary nav is fully keyboard operable', async ({ page }) => {\n  await page.goto('\u002F');\n  \u002F\u002F Start from a known anchor so the tab sequence is deterministic.\n  await page.getByRole('link', { name: 'Skip to content' }).focus();\n\n  await page.keyboard.press('Tab');\n  \u002F\u002F A11y rationale: the first interactive control must be reachable by keyboard.\n  await expect(page.getByRole('link', { name: 'Home' })).toBeFocused();\n\n  await page.keyboard.press('Tab');\n  await expect(page.getByRole('link', { name: 'Pricing' })).toBeFocused();\n\n  \u002F\u002F Activate with Enter, exactly as a keyboard user would.\n  await page.keyboard.press('Enter');\n  await expect(page).toHaveURL(\u002F\\\u002Fpricing\u002F);\n});\n",[336,1160,1161,1184,1199,1204,1229,1233,1250,1255,1284,1288,1302,1327,1331,1336,1351,1379],{"__ignoreMap":648},[652,1162,1163,1165,1167,1170,1172,1174,1176,1178,1180,1182],{"class":654,"line":655},[652,1164,702],{"class":701},[652,1166,705],{"class":662},[652,1168,1169],{"class":669},"'primary nav is fully keyboard operable'",[652,1171,634],{"class":662},[652,1173,713],{"class":658},[652,1175,716],{"class":662},[652,1177,720],{"class":719},[652,1179,723],{"class":662},[652,1181,726],{"class":658},[652,1183,729],{"class":662},[652,1185,1186,1188,1190,1192,1194,1197],{"class":654,"line":676},[652,1187,735],{"class":658},[652,1189,738],{"class":662},[652,1191,741],{"class":701},[652,1193,705],{"class":662},[652,1195,1196],{"class":669},"'\u002F'",[652,1198,749],{"class":662},[652,1200,1201],{"class":654,"line":691},[652,1202,1203],{"class":755},"  \u002F\u002F Start from a known anchor so the tab sequence is deterministic.\n",[652,1205,1206,1208,1210,1212,1214,1217,1219,1222,1224,1227],{"class":654,"line":698},[652,1207,735],{"class":658},[652,1209,738],{"class":662},[652,1211,766],{"class":701},[652,1213,705],{"class":662},[652,1215,1216],{"class":669},"'link'",[652,1218,774],{"class":662},[652,1220,1221],{"class":669},"'Skip to content'",[652,1223,780],{"class":662},[652,1225,1226],{"class":701},"focus",[652,1228,786],{"class":662},[652,1230,1231],{"class":654,"line":732},[652,1232,695],{"emptyLinePlaceholder":694},[652,1234,1235,1237,1240,1243,1245,1248],{"class":654,"line":752},[652,1236,735],{"class":658},[652,1238,1239],{"class":662}," page.keyboard.",[652,1241,1242],{"class":701},"press",[652,1244,705],{"class":662},[652,1246,1247],{"class":669},"'Tab'",[652,1249,749],{"class":662},[652,1251,1252],{"class":654,"line":759},[652,1253,1254],{"class":755},"  \u002F\u002F A11y rationale: the first interactive control must be reachable by keyboard.\n",[652,1256,1257,1259,1262,1265,1267,1269,1271,1273,1276,1279,1282],{"class":654,"line":789},[652,1258,735],{"class":658},[652,1260,1261],{"class":701}," expect",[652,1263,1264],{"class":662},"(page.",[652,1266,766],{"class":701},[652,1268,705],{"class":662},[652,1270,1216],{"class":669},[652,1272,774],{"class":662},[652,1274,1275],{"class":669},"'Home'",[652,1277,1278],{"class":662}," })).",[652,1280,1281],{"class":701},"toBeFocused",[652,1283,786],{"class":662},[652,1285,1286],{"class":654,"line":794},[652,1287,695],{"emptyLinePlaceholder":694},[652,1289,1290,1292,1294,1296,1298,1300],{"class":654,"line":819},[652,1291,735],{"class":658},[652,1293,1239],{"class":662},[652,1295,1242],{"class":701},[652,1297,705],{"class":662},[652,1299,1247],{"class":669},[652,1301,749],{"class":662},[652,1303,1304,1306,1308,1310,1312,1314,1316,1318,1321,1323,1325],{"class":654,"line":825},[652,1305,735],{"class":658},[652,1307,1261],{"class":701},[652,1309,1264],{"class":662},[652,1311,766],{"class":701},[652,1313,705],{"class":662},[652,1315,1216],{"class":669},[652,1317,774],{"class":662},[652,1319,1320],{"class":669},"'Pricing'",[652,1322,1278],{"class":662},[652,1324,1281],{"class":701},[652,1326,786],{"class":662},[652,1328,1329],{"class":654,"line":848},[652,1330,695],{"emptyLinePlaceholder":694},[652,1332,1333],{"class":654,"line":858},[652,1334,1335],{"class":755},"  \u002F\u002F Activate with Enter, exactly as a keyboard user would.\n",[652,1337,1338,1340,1342,1344,1346,1349],{"class":654,"line":863},[652,1339,735],{"class":658},[652,1341,1239],{"class":662},[652,1343,1242],{"class":701},[652,1345,705],{"class":662},[652,1347,1348],{"class":669},"'Enter'",[652,1350,749],{"class":662},[652,1352,1353,1355,1357,1360,1363,1365,1367,1371,1375,1377],{"class":654,"line":869},[652,1354,735],{"class":658},[652,1356,1261],{"class":701},[652,1358,1359],{"class":662},"(page).",[652,1361,1362],{"class":701},"toHaveURL",[652,1364,705],{"class":662},[652,1366,86],{"class":669},[652,1368,1370],{"class":1369},"snhLl","\\\u002F",[652,1372,1374],{"class":1373},"sA_wV","pricing",[652,1376,86],{"class":669},[652,1378,749],{"class":662},[652,1380,1381],{"class":654,"line":875},[652,1382,893],{"class":662},[324,1384,1385,1386,1037],{},"This pattern scales into complete dropdown and menu interactions. For the full sequence of opening a menu, arrowing through items, and closing it, see the dedicated guide on ",[328,1387,1389],{"href":1388},"\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright\u002F","keyboard navigation tests in Playwright",[324,1391,1392,1394,1395,1397],{},[353,1393,596],{}," Unplug or ignore the mouse and reproduce the test path by hand. If you reach the same controls in the same order, the test mirrors reality. Confirm ",[336,1396,374],{}," by watching for a visible focus ring at each step.",[409,1399],{},[412,1401,1403],{"id":1402},"asserting-focus-with-tobefocused","Asserting Focus with toBeFocused()",[324,1405,1406,1407,1409,1410,1412],{},"Focus is the single most important runtime state for keyboard and screen-reader users, and it is also the easiest to assert directly. Playwright's ",[336,1408,403],{}," matcher checks the live ",[336,1411,457],{}," against a locator, with auto-waiting built in.",[324,1414,1415],{},[353,1416,618],{},[357,1418,1419,1422,1425],{},[360,1420,1421],{},"Assert focus immediately after the action that should move it, never several steps later.",[360,1423,1424],{},"Prefer role-based locators so the assertion describes intent, not DOM structure.",[360,1426,1427,1428,1431],{},"When focus ",[341,1429,1430],{},"should not"," move, assert that the previously focused element stays focused.",[643,1433,1435],{"className":645,"code":1434,"language":647,"meta":648,"style":648},"test('opening the dialog moves focus into it', async ({ page }) => {\n  await page.goto('\u002Faccount');\n\n  const trigger = page.getByRole('button', { name: 'Edit profile' });\n  await trigger.focus();\n  await page.keyboard.press('Enter');\n\n  const dialog = page.getByRole('dialog', { name: 'Edit profile' });\n  await expect(dialog).toBeVisible();\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — focus must enter the dialog on open\n  \u002F\u002F so keyboard and screen-reader users are not stranded behind it.\n  await expect(dialog.getByRole('textbox', { name: 'Display name' })).toBeFocused();\n});\n",[336,1436,1437,1460,1475,1479,1505,1516,1530,1534,1558,1572,1576,1581,1586,1613],{"__ignoreMap":648},[652,1438,1439,1441,1443,1446,1448,1450,1452,1454,1456,1458],{"class":654,"line":655},[652,1440,702],{"class":701},[652,1442,705],{"class":662},[652,1444,1445],{"class":669},"'opening the dialog moves focus into it'",[652,1447,634],{"class":662},[652,1449,713],{"class":658},[652,1451,716],{"class":662},[652,1453,720],{"class":719},[652,1455,723],{"class":662},[652,1457,726],{"class":658},[652,1459,729],{"class":662},[652,1461,1462,1464,1466,1468,1470,1473],{"class":654,"line":676},[652,1463,735],{"class":658},[652,1465,738],{"class":662},[652,1467,741],{"class":701},[652,1469,705],{"class":662},[652,1471,1472],{"class":669},"'\u002Faccount'",[652,1474,749],{"class":662},[652,1476,1477],{"class":654,"line":691},[652,1478,695],{"emptyLinePlaceholder":694},[652,1480,1481,1483,1486,1488,1490,1492,1494,1497,1499,1502],{"class":654,"line":698},[652,1482,797],{"class":658},[652,1484,1485],{"class":800}," trigger",[652,1487,804],{"class":658},[652,1489,738],{"class":662},[652,1491,766],{"class":701},[652,1493,705],{"class":662},[652,1495,1496],{"class":669},"'button'",[652,1498,774],{"class":662},[652,1500,1501],{"class":669},"'Edit profile'",[652,1503,1504],{"class":662}," });\n",[652,1506,1507,1509,1512,1514],{"class":654,"line":732},[652,1508,735],{"class":658},[652,1510,1511],{"class":662}," trigger.",[652,1513,1226],{"class":701},[652,1515,786],{"class":662},[652,1517,1518,1520,1522,1524,1526,1528],{"class":654,"line":752},[652,1519,735],{"class":658},[652,1521,1239],{"class":662},[652,1523,1242],{"class":701},[652,1525,705],{"class":662},[652,1527,1348],{"class":669},[652,1529,749],{"class":662},[652,1531,1532],{"class":654,"line":759},[652,1533,695],{"emptyLinePlaceholder":694},[652,1535,1536,1538,1541,1543,1545,1547,1549,1552,1554,1556],{"class":654,"line":789},[652,1537,797],{"class":658},[652,1539,1540],{"class":800}," dialog",[652,1542,804],{"class":658},[652,1544,738],{"class":662},[652,1546,766],{"class":701},[652,1548,705],{"class":662},[652,1550,1551],{"class":669},"'dialog'",[652,1553,774],{"class":662},[652,1555,1501],{"class":669},[652,1557,1504],{"class":662},[652,1559,1560,1562,1564,1567,1570],{"class":654,"line":794},[652,1561,735],{"class":658},[652,1563,1261],{"class":701},[652,1565,1566],{"class":662},"(dialog).",[652,1568,1569],{"class":701},"toBeVisible",[652,1571,786],{"class":662},[652,1573,1574],{"class":654,"line":819},[652,1575,695],{"emptyLinePlaceholder":694},[652,1577,1578],{"class":654,"line":825},[652,1579,1580],{"class":755},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — focus must enter the dialog on open\n",[652,1582,1583],{"class":654,"line":848},[652,1584,1585],{"class":755},"  \u002F\u002F so keyboard and screen-reader users are not stranded behind it.\n",[652,1587,1588,1590,1592,1595,1597,1599,1602,1604,1607,1609,1611],{"class":654,"line":858},[652,1589,735],{"class":658},[652,1591,1261],{"class":701},[652,1593,1594],{"class":662},"(dialog.",[652,1596,766],{"class":701},[652,1598,705],{"class":662},[652,1600,1601],{"class":669},"'textbox'",[652,1603,774],{"class":662},[652,1605,1606],{"class":669},"'Display name'",[652,1608,1278],{"class":662},[652,1610,1281],{"class":701},[652,1612,786],{"class":662},[652,1614,1615],{"class":654,"line":863},[652,1616,893],{"class":662},[324,1618,1619,1620,1623,1624,1037],{},"Asserting focus order across a sequence, and verifying that focus never silently drops to ",[336,1621,1622],{},"\u003Cbody>",", is a deep enough topic to warrant its own treatment in ",[328,1625,1627],{"href":1626},"\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright\u002F","asserting focus order in Playwright",[324,1629,1630,1632,1633,1636],{},[353,1631,596],{}," Temporarily break the focus call in the component (comment out the ",[336,1634,1635],{},".focus()","), and confirm the assertion fails. With a real screen reader open, confirm the dialog's name and the first field are announced on open.",[409,1638],{},[412,1640,1642],{"id":1641},"verifying-aria-live-region-announcements","Verifying ARIA Live Region Announcements",[324,1644,1645,1646,1649],{},"A live region only helps users if its text content actually changes in the DOM after an action—screen readers announce the ",[341,1647,1648],{},"mutation",", not your intent. End-to-end tests verify this by reading the region's text after triggering the event that should update it.",[324,1651,1652],{},[353,1653,618],{},[357,1655,1656,1670,1673],{},[360,1657,1658,1659,1662,1663,1666,1667,1037],{},"Locate the live region by role (",[336,1660,1661],{},"status"," for polite, ",[336,1664,1665],{},"alert"," for assertive) or by ",[336,1668,1669],{},"aria-live",[360,1671,1672],{},"Trigger the action, then assert the region's text with auto-waiting so async updates are captured.",[360,1674,1675],{},"Confirm the politeness level matches the urgency of the message.",[643,1677,1679],{"className":645,"code":1678,"language":647,"meta":648,"style":648},"test('adding to cart announces a polite status update', async ({ page }) => {\n  await page.goto('\u002Fproducts\u002Fwidget');\n\n  \u002F\u002F The live region is mounted up front so screen readers observe its mutations.\n  const status = page.getByRole('status');\n\n  await page.getByRole('button', { name: 'Add to cart' }).click();\n\n  \u002F\u002F A11y rationale: 4.1.3 Status Messages — the announcement must reach the\n  \u002F\u002F DOM. toHaveText auto-waits for the async mutation to land.\n  await expect(status).toHaveText('Widget added to cart');\n});\n\ntest('form error is announced assertively', async ({ page }) => {\n  await page.goto('\u002Fsignup');\n\n  await page.getByRole('button', { name: 'Create account' }).click();\n\n  const alert = page.getByRole('alert');\n  await expect(alert).toBeVisible();\n  await expect(alert).toContainText('Email is required');\n});\n",[336,1680,1681,1704,1719,1723,1728,1748,1752,1776,1780,1785,1790,1809,1813,1817,1840,1855,1859,1882,1887,1908,1922,1941],{"__ignoreMap":648},[652,1682,1683,1685,1687,1690,1692,1694,1696,1698,1700,1702],{"class":654,"line":655},[652,1684,702],{"class":701},[652,1686,705],{"class":662},[652,1688,1689],{"class":669},"'adding to cart announces a polite status update'",[652,1691,634],{"class":662},[652,1693,713],{"class":658},[652,1695,716],{"class":662},[652,1697,720],{"class":719},[652,1699,723],{"class":662},[652,1701,726],{"class":658},[652,1703,729],{"class":662},[652,1705,1706,1708,1710,1712,1714,1717],{"class":654,"line":676},[652,1707,735],{"class":658},[652,1709,738],{"class":662},[652,1711,741],{"class":701},[652,1713,705],{"class":662},[652,1715,1716],{"class":669},"'\u002Fproducts\u002Fwidget'",[652,1718,749],{"class":662},[652,1720,1721],{"class":654,"line":691},[652,1722,695],{"emptyLinePlaceholder":694},[652,1724,1725],{"class":654,"line":698},[652,1726,1727],{"class":755},"  \u002F\u002F The live region is mounted up front so screen readers observe its mutations.\n",[652,1729,1730,1732,1735,1737,1739,1741,1743,1746],{"class":654,"line":732},[652,1731,797],{"class":658},[652,1733,1734],{"class":800}," status",[652,1736,804],{"class":658},[652,1738,738],{"class":662},[652,1740,766],{"class":701},[652,1742,705],{"class":662},[652,1744,1745],{"class":669},"'status'",[652,1747,749],{"class":662},[652,1749,1750],{"class":654,"line":752},[652,1751,695],{"emptyLinePlaceholder":694},[652,1753,1754,1756,1758,1760,1762,1764,1766,1769,1771,1774],{"class":654,"line":759},[652,1755,735],{"class":658},[652,1757,738],{"class":662},[652,1759,766],{"class":701},[652,1761,705],{"class":662},[652,1763,1496],{"class":669},[652,1765,774],{"class":662},[652,1767,1768],{"class":669},"'Add to cart'",[652,1770,780],{"class":662},[652,1772,1773],{"class":701},"click",[652,1775,786],{"class":662},[652,1777,1778],{"class":654,"line":789},[652,1779,695],{"emptyLinePlaceholder":694},[652,1781,1782],{"class":654,"line":794},[652,1783,1784],{"class":755},"  \u002F\u002F A11y rationale: 4.1.3 Status Messages — the announcement must reach the\n",[652,1786,1787],{"class":654,"line":819},[652,1788,1789],{"class":755},"  \u002F\u002F DOM. toHaveText auto-waits for the async mutation to land.\n",[652,1791,1792,1794,1796,1799,1802,1804,1807],{"class":654,"line":825},[652,1793,735],{"class":658},[652,1795,1261],{"class":701},[652,1797,1798],{"class":662},"(status).",[652,1800,1801],{"class":701},"toHaveText",[652,1803,705],{"class":662},[652,1805,1806],{"class":669},"'Widget added to cart'",[652,1808,749],{"class":662},[652,1810,1811],{"class":654,"line":848},[652,1812,893],{"class":662},[652,1814,1815],{"class":654,"line":858},[652,1816,695],{"emptyLinePlaceholder":694},[652,1818,1819,1821,1823,1826,1828,1830,1832,1834,1836,1838],{"class":654,"line":863},[652,1820,702],{"class":701},[652,1822,705],{"class":662},[652,1824,1825],{"class":669},"'form error is announced assertively'",[652,1827,634],{"class":662},[652,1829,713],{"class":658},[652,1831,716],{"class":662},[652,1833,720],{"class":719},[652,1835,723],{"class":662},[652,1837,726],{"class":658},[652,1839,729],{"class":662},[652,1841,1842,1844,1846,1848,1850,1853],{"class":654,"line":869},[652,1843,735],{"class":658},[652,1845,738],{"class":662},[652,1847,741],{"class":701},[652,1849,705],{"class":662},[652,1851,1852],{"class":669},"'\u002Fsignup'",[652,1854,749],{"class":662},[652,1856,1857],{"class":654,"line":875},[652,1858,695],{"emptyLinePlaceholder":694},[652,1860,1861,1863,1865,1867,1869,1871,1873,1876,1878,1880],{"class":654,"line":890},[652,1862,735],{"class":658},[652,1864,738],{"class":662},[652,1866,766],{"class":701},[652,1868,705],{"class":662},[652,1870,1496],{"class":669},[652,1872,774],{"class":662},[652,1874,1875],{"class":669},"'Create account'",[652,1877,780],{"class":662},[652,1879,1773],{"class":701},[652,1881,786],{"class":662},[652,1883,1885],{"class":654,"line":1884},18,[652,1886,695],{"emptyLinePlaceholder":694},[652,1888,1890,1892,1895,1897,1899,1901,1903,1906],{"class":654,"line":1889},19,[652,1891,797],{"class":658},[652,1893,1894],{"class":800}," alert",[652,1896,804],{"class":658},[652,1898,738],{"class":662},[652,1900,766],{"class":701},[652,1902,705],{"class":662},[652,1904,1905],{"class":669},"'alert'",[652,1907,749],{"class":662},[652,1909,1911,1913,1915,1918,1920],{"class":654,"line":1910},20,[652,1912,735],{"class":658},[652,1914,1261],{"class":701},[652,1916,1917],{"class":662},"(alert).",[652,1919,1569],{"class":701},[652,1921,786],{"class":662},[652,1923,1925,1927,1929,1931,1934,1936,1939],{"class":654,"line":1924},21,[652,1926,735],{"class":658},[652,1928,1261],{"class":701},[652,1930,1917],{"class":662},[652,1932,1933],{"class":701},"toContainText",[652,1935,705],{"class":662},[652,1937,1938],{"class":669},"'Email is required'",[652,1940,749],{"class":662},[652,1942,1944],{"class":654,"line":1943},22,[652,1945,893],{"class":662},[324,1947,1948,1949,1952],{},"Note that Playwright verifies the DOM mutation, not synthesized speech. Pair these assertions with periodic manual NVDA or VoiceOver checks to confirm the announcement is actually spoken and not swallowed by an over-eager ",[336,1950,1951],{},"aria-atomic"," configuration.",[324,1954,1955,1957,1958,1961,1962,1965],{},[353,1956,596],{}," Run the test, then repeat the action manually with a screen reader running and confirm you hear the same text. Flip a region from ",[336,1959,1960],{},"polite"," to ",[336,1963,1964],{},"assertive"," and confirm the urgency change is justified by the message.",[409,1967],{},[412,1969,1971],{"id":1970},"testing-focus-restoration-across-route-changes","Testing Focus Restoration Across Route Changes",[324,1973,1974,1975,1978,1979,1981],{},"In a single-page app, the framework swaps the view without a full page load, which means the browser does ",[341,1976,1977],{},"not"," reset focus. Without explicit handling, focus is left on a control that no longer exists or drops to ",[336,1980,1622],{},", stranding keyboard and screen-reader users. End-to-end tests are the only place this behavior can be reliably proven.",[324,1983,1984],{},[353,1985,618],{},[357,1987,1988,1991,1994],{},[360,1989,1990],{},"After client-side navigation, assert that focus lands on a meaningful target (the new page heading or main landmark).",[360,1992,1993],{},"After closing an overlay, assert focus returns to the element that opened it.",[360,1995,1996,1997,1037],{},"Explicitly guard against focus falling to ",[336,1998,1999],{},"document.body",[643,2001,2003],{"className":645,"code":2002,"language":647,"meta":648,"style":648},"test('client-side navigation moves focus to the new page heading', async ({ page }) => {\n  await page.goto('\u002F');\n\n  await page.getByRole('link', { name: 'Pricing' }).focus();\n  await page.keyboard.press('Enter');\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — after an SPA route change, focus must\n  \u002F\u002F move to a logical target so the next Tab continues from the right place.\n  const heading = page.getByRole('heading', { level: 1, name: 'Pricing' });\n  await expect(heading).toBeFocused();\n\n  \u002F\u002F Guard: focus must never silently fall back to the document body.\n  const onBody = await page.evaluate(() => document.activeElement === document.body);\n  expect(onBody).toBe(false);\n});\n",[336,2004,2005,2028,2042,2046,2068,2082,2086,2091,2096,2126,2139,2143,2148,2178,2195],{"__ignoreMap":648},[652,2006,2007,2009,2011,2014,2016,2018,2020,2022,2024,2026],{"class":654,"line":655},[652,2008,702],{"class":701},[652,2010,705],{"class":662},[652,2012,2013],{"class":669},"'client-side navigation moves focus to the new page heading'",[652,2015,634],{"class":662},[652,2017,713],{"class":658},[652,2019,716],{"class":662},[652,2021,720],{"class":719},[652,2023,723],{"class":662},[652,2025,726],{"class":658},[652,2027,729],{"class":662},[652,2029,2030,2032,2034,2036,2038,2040],{"class":654,"line":676},[652,2031,735],{"class":658},[652,2033,738],{"class":662},[652,2035,741],{"class":701},[652,2037,705],{"class":662},[652,2039,1196],{"class":669},[652,2041,749],{"class":662},[652,2043,2044],{"class":654,"line":691},[652,2045,695],{"emptyLinePlaceholder":694},[652,2047,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066],{"class":654,"line":698},[652,2049,735],{"class":658},[652,2051,738],{"class":662},[652,2053,766],{"class":701},[652,2055,705],{"class":662},[652,2057,1216],{"class":669},[652,2059,774],{"class":662},[652,2061,1320],{"class":669},[652,2063,780],{"class":662},[652,2065,1226],{"class":701},[652,2067,786],{"class":662},[652,2069,2070,2072,2074,2076,2078,2080],{"class":654,"line":732},[652,2071,735],{"class":658},[652,2073,1239],{"class":662},[652,2075,1242],{"class":701},[652,2077,705],{"class":662},[652,2079,1348],{"class":669},[652,2081,749],{"class":662},[652,2083,2084],{"class":654,"line":752},[652,2085,695],{"emptyLinePlaceholder":694},[652,2087,2088],{"class":654,"line":759},[652,2089,2090],{"class":755},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — after an SPA route change, focus must\n",[652,2092,2093],{"class":654,"line":789},[652,2094,2095],{"class":755},"  \u002F\u002F move to a logical target so the next Tab continues from the right place.\n",[652,2097,2098,2100,2103,2105,2107,2109,2111,2113,2116,2119,2122,2124],{"class":654,"line":794},[652,2099,797],{"class":658},[652,2101,2102],{"class":800}," heading",[652,2104,804],{"class":658},[652,2106,738],{"class":662},[652,2108,766],{"class":701},[652,2110,705],{"class":662},[652,2112,771],{"class":669},[652,2114,2115],{"class":662},", { level: ",[652,2117,2118],{"class":800},"1",[652,2120,2121],{"class":662},", name: ",[652,2123,1320],{"class":669},[652,2125,1504],{"class":662},[652,2127,2128,2130,2132,2135,2137],{"class":654,"line":819},[652,2129,735],{"class":658},[652,2131,1261],{"class":701},[652,2133,2134],{"class":662},"(heading).",[652,2136,1281],{"class":701},[652,2138,786],{"class":662},[652,2140,2141],{"class":654,"line":825},[652,2142,695],{"emptyLinePlaceholder":694},[652,2144,2145],{"class":654,"line":848},[652,2146,2147],{"class":755},"  \u002F\u002F Guard: focus must never silently fall back to the document body.\n",[652,2149,2150,2152,2155,2157,2159,2161,2164,2167,2169,2172,2175],{"class":654,"line":858},[652,2151,797],{"class":658},[652,2153,2154],{"class":800}," onBody",[652,2156,804],{"class":658},[652,2158,807],{"class":658},[652,2160,738],{"class":662},[652,2162,2163],{"class":701},"evaluate",[652,2165,2166],{"class":662},"(() ",[652,2168,726],{"class":658},[652,2170,2171],{"class":662}," document.activeElement ",[652,2173,2174],{"class":658},"===",[652,2176,2177],{"class":662}," document.body);\n",[652,2179,2180,2182,2185,2188,2190,2193],{"class":654,"line":863},[652,2181,878],{"class":701},[652,2183,2184],{"class":662},"(onBody).",[652,2186,2187],{"class":701},"toBe",[652,2189,705],{"class":662},[652,2191,2192],{"class":800},"false",[652,2194,749],{"class":662},[652,2196,2197],{"class":654,"line":869},[652,2198,893],{"class":662},[324,2200,2201,2202,2205,2206,1037],{},"The framework-side patterns this test validates are covered in ",[328,2203,25],{"href":2204},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002F",". Once these tests are green locally, wire them into your delivery pipeline so regressions are blocked before merge—see ",[328,2207,2209],{"href":2208},"\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002F","gating accessibility in CI\u002FCD pipelines",[324,2211,2212,2214,2215,1037],{},[353,2213,596],{}," Navigate the route by keyboard with a screen reader running and confirm the new page title is announced. Comment out the framework's focus-on-navigation logic and confirm the test fails by detecting focus on ",[336,2216,1622],{},[409,2218],{},[412,2220,2222],{"id":2221},"common-a11y-mistakes","Common a11y Mistakes",[357,2224,2225,2238,2248,2256,2269,2278],{},[360,2226,2227,2233,2234,2237],{},[353,2228,2229,2230,2232],{},"Driving flows with ",[336,2231,1113],{}," instead of the keyboard."," Clicks prove pointer operability only; they let keyboard-inaccessible UI pass. Use ",[336,2235,2236],{},"page.keyboard.press()"," for any flow that claims keyboard support.",[360,2239,2240,2243,2244,2247],{},[353,2241,2242],{},"Scanning before the page settles."," Running ",[336,2245,2246],{},"AxeBuilder().analyze()"," against a loading skeleton yields false passes. Wait for a stable, known element first.",[360,2249,2250,2253,2254,1037],{},[353,2251,2252],{},"Inferring focus from side effects."," Asserting that a URL changed does not prove focus moved. Assert focus directly with ",[336,2255,403],{},[360,2257,2258,2261,2262,2264,2265,2268],{},[353,2259,2260],{},"Asserting live-region text synchronously."," Announcements often land a tick after the action. Use auto-waiting matchers like ",[336,2263,1801],{}," rather than reading ",[336,2266,2267],{},"textContent"," once.",[360,2270,2271,2274,2275,2277],{},[353,2272,2273],{},"Ignoring focus restoration after route changes."," SPA navigation does not reset focus; without an explicit assertion, focus loss to ",[336,2276,1622],{}," ships unnoticed.",[360,2279,2280,2283],{},[353,2281,2282],{},"Treating Axe as complete coverage."," Automated rules catch roughly a third of issues. Keyboard, focus, and announcement assertions cover behavior Axe cannot see.",[409,2285],{},[412,2287,2289],{"id":2288},"frequently-asked-questions","Frequently Asked Questions",[324,2291,2292,2295,2296,2298],{},[353,2293,2294],{},"Does @axe-core\u002Fplaywright replace my jest-axe component tests?","\nNo. They operate at different layers and catch different bugs. jest-axe gives fast, isolated feedback on a single component in jsdom; ",[336,2297,338],{}," scans the fully rendered application in a real browser, where contrast, layout, and focus actually resolve. Run both, and reserve end-to-end scans for assembled flows.",[324,2300,2301,2307,2308,2310,2311,2313],{},[353,2302,2303,2304,2306],{},"Why not just use ",[336,2305,1113],{}," to drive my flows?","\nBecause a click is a pointer event and proves nothing about keyboard operability. A control bound only to mouse events will pass a click-driven test while failing ",[336,2309,364],{}," completely. Use ",[336,2312,2236],{}," so your tests fail when a control cannot be reached or activated without a mouse.",[324,2315,2316,2319,2320,2323,2324,2326],{},[353,2317,2318],{},"How do I verify a screen reader actually announces something?","\nEnd-to-end tests verify the DOM mutation that drives the announcement—for example, that a ",[336,2321,2322],{},"role=\"status\""," region's text changes after an action. They do not capture synthesized speech. Pair the automated assertion with periodic manual NVDA or VoiceOver checks to confirm the text is spoken and not suppressed by ",[336,2325,1951],{}," or visibility issues.",[324,2328,2329,2332,2333,2335],{},[353,2330,2331],{},"Can Playwright detect color contrast problems?","\nYes, when you scan with ",[336,2334,338],{}," in a real browser. Axe reads computed foreground and background colors from the layout engine, which jsdom cannot provide. This is a primary reason to run contrast-sensitive checks end-to-end rather than at the unit level.",[324,2337,2338,2341],{},[353,2339,2340],{},"Should accessibility tests run on every browser project?","\nRun Axe scans and core keyboard flows on at least Chromium for speed, and extend critical paths to WebKit and Firefox. Focus and live-region behavior can differ subtly across engines, so cross-browser coverage on your highest-value flows is worth the runtime.",[409,2343],{},[412,2345,2347],{"id":2346},"related-guides","Related guides",[357,2349,2350,2355,2360,2365,2370,2375],{},[360,2351,2352,2354],{},[328,2353,348],{"href":347}," — the guide for your automated accessibility strategy.",[360,2356,2357,2359],{},[328,2358,261],{"href":474}," — fast, isolated unit-level scans.",[360,2361,2362,2364],{},[328,2363,225],{"href":1094}," — broad scored snapshots for prioritization.",[360,2366,2367,2369],{},[328,2368,297],{"href":2208}," — block regressions before merge.",[360,2371,2372,2374],{},[328,2373,291],{"href":1388}," — tab, activate, and Escape flows in depth.",[360,2376,2377,2379],{},[328,2378,285],{"href":1626}," — capture and lock down the focus sequence.",[2381,2382,2383],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":648,"searchDepth":676,"depth":676,"links":2385},[2386,2387,2388,2389,2390,2391,2392,2393,2394],{"id":414,"depth":676,"text":415},{"id":606,"depth":676,"text":607},{"id":1106,"depth":676,"text":1107},{"id":1402,"depth":676,"text":1403},{"id":1641,"depth":676,"text":1642},{"id":1970,"depth":676,"text":1971},{"id":2221,"depth":676,"text":2222},{"id":2288,"depth":676,"text":2289},{"id":2346,"depth":676,"text":2347},null,"Validate real keyboard, focus, and live-region behavior in the browser with Playwright and @axe-core\u002Fplaywright across full user flows—not just static markup.","md",{},false,{"title":279,"description":2396},"JMCfDdVq-LFEUdSHHgiOnqnIjxqPSya_FTTYdsINLaY",[2403,2442,2443,2506],{"title":5,"path":6,"stem":7,"children":2404},[2405,2406,2409,2412,2418,2424,2433,2439],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2407},[2408],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2410},[2411],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2413},[2414,2415],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2416},[2417],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2419},[2420,2421],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2422},[2423],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2425},[2426,2427,2430],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2428},[2429],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2431},[2432],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2434},[2435,2436],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2437},[2438],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2440},[2441],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2444},[2445,2446,2452,2464,2476,2479,2488,2500],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2447},[2448,2449],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2450},[2451],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2453},[2454,2455,2458,2461],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2456},[2457],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2459},[2460],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2462},[2463],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2465},[2466,2467,2470,2473],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2468},[2469],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2471},[2472],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2474},[2475],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":2477},[2478],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":2480},[2481,2482,2485],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":2483},[2484],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":2486},[2487],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":2489},[2490,2491,2494,2497],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":2492},[2493],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":2495},[2496],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":2498},[2499],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":2501},[2502,2503],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":2504},[2505],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":2507},[2508,2509,2518,2527,2536,2545],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":2510},[2511,2512,2515],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":2513},[2514],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":2516},[2517],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":2519},[2520,2521,2524],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":2522},[2523],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":2525},[2526],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":2528},[2529,2530,2533],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":2531},[2532],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":2534},[2535],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":2537},[2538,2539,2542],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":2540},[2541],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":2543},[2544],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":2546},[2547,2548,2551],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":2549},[2550],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":2552},[2553],{"title":309,"path":310,"stem":311},1781785523930]