diff --git a/README.md b/README.md index c54a1b6..4e76507 100644 --- a/README.md +++ b/README.md @@ -134,10 +134,12 @@ Defines the scope of the analysis - the part of the DOM that you would like to a Set of options passed into rules or checks, temporarily modifying them. This contrasts with axe.configure, which is more permanent. -The keys consist of [those accepted by `axe.run`'s options argument](https://www.deque.com/axe/documentation/api-documentation/#parameters-axerun) as well as a custom `includedImpacts` key. +The keys consist of [those accepted by `axe.run`'s options argument](https://www.deque.com/axe/documentation/api-documentation/#parameters-axerun), a custom `includedImpacts` key, and `retries`/`interval` keys for retrying the check. The `includedImpacts` key is an array of strings that map to `impact` levels in violations. Specifying this array will only include violations where the impact matches one of the included values. Possible impact values are "minor", "moderate", "serious", or "critical". +The `retries` key is an integer that specifies how many times to retry the check if there are initial findings. The `interval` key is an integer that specifies the number of milliseconds to wait between retries, and defaults to `1000` (one second). If `retries` is not specified, the check will only be run once. Use this option to account for dynamic content that may not be fully loaded when the check is first run. + Filtering based on impact in combination with the `skipFailures` argument allows you to introduce `cypress-axe` into tests for a legacy application without failing in CI before you have an opportunity to address accessibility issues. Ideally, you would steadily move towards stricter testing as you address issues. ##### violationCallback (optional) @@ -192,6 +194,14 @@ it('Only logs a11y violations while allowing the test to pass', () => { // Do not fail the test when there are accessibility failures cy.checkA11y(null, null, null, true) }) + +it('Has no a11y violations after asynchronous load', () => { + // Retry the check if there are initial failures + cy.checkA11y(null, { + retries: 3, + interval: 100 + }) +}) ``` #### Using the violationCallback argument diff --git a/cypress/e2e/test.cy.js b/cypress/e2e/test.cy.js index 9350f23..685350b 100644 --- a/cypress/e2e/test.cy.js +++ b/cypress/e2e/test.cy.js @@ -1,5 +1,5 @@ it('works!', () => { cy.visit('/'); cy.injectAxe(); - cy.checkA11y(); + cy.checkA11y(undefined, { retries: 20, interval: 10 }); }); diff --git a/src/index.ts b/src/index.ts index 6fbd163..70654db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,8 @@ declare global { export interface Options extends axe.RunOptions { includedImpacts?: string[]; + interval?: number; + retries?: number; } export interface InjectOptions { @@ -51,6 +53,17 @@ function isEmptyObjectorNull(value: any) { return Object.entries(value).length === 0 && value.constructor === Object; } +function summarizeResults( + includedImpacts: string[] | undefined, + violations: axe.Result[] +): axe.Result[] { + return includedImpacts && + Array.isArray(includedImpacts) && + Boolean(includedImpacts.length) + ? violations.filter((v) => v.impact && includedImpacts.includes(v.impact)) + : violations; +} + const checkA11y = ( context?: axe.ElementContext, options?: Options, @@ -68,18 +81,25 @@ const checkA11y = ( if (isEmptyObjectorNull(violationCallback)) { violationCallback = undefined; } - const { includedImpacts, ...axeOptions } = options || {}; - return win.axe - .run(context || win.document, axeOptions) - .then(({ violations }) => { - return includedImpacts && - Array.isArray(includedImpacts) && - Boolean(includedImpacts.length) - ? violations.filter( - (v) => v.impact && includedImpacts.includes(v.impact) - ) - : violations; - }); + const { includedImpacts, interval, retries, ...axeOptions } = + options || {}; + let remainingRetries = retries || 0; + function runAxeCheck(): Promise { + return win.axe + .run(context || win.document, axeOptions) + .then(({ violations }) => { + const results = summarizeResults(includedImpacts, violations); + if (results.length > 0 && remainingRetries > 0) { + remainingRetries--; + return new Promise((resolve) => { + setTimeout(resolve, interval || 1000); + }).then(runAxeCheck); + } else { + return results; + } + }); + } + return runAxeCheck(); }) .then((violations) => { if (violations.length) { diff --git a/test/index.html b/test/index.html index 4691bfa..f84523c 100644 --- a/test/index.html +++ b/test/index.html @@ -9,6 +9,14 @@

Cypress-axe test

This is cypress-axe test.

+
+