axe.runPartial
and axe.finishRun
are two methods which allow axe to test a page in two stages. This is different from axe.run()
, in that axe.runPartial
and axe.finishRun
does not require communication between frames. This can be useful when frame communication is not possible or not secure. See axe.frameMessenger for information on frame communication.
To use these methods, call axe.runPartial()
in the top window, and in all nested frames and iframes. The results are then put into an array, and then passed to axe.finishRun()
. axe.finishRun()
then complete the test and generates the axe report.
This results in code that looks something like the following. The context
and options
arguments used are the same as would be passed to axe.run
. See API.md for details.
const partialResults = await Promise.all(runPartialRecursive(context, options));
const axeResults = await axe.finishRun(partialResults, options);
note: The code in this page uses native DOM methods. This will only work on frames with the same origin. Scripts do not have access to contentWindow
of cross-origin frames. Use of runPartial
and finishRun
in browser drivers like Selenium and Puppeteer works in the same way.
note: Because axe.runPartial()
is designed to be serialized, it will not return element references even if the elementRef
option is set.
When using axe.runPartial()
it is important to keep in mind that the context
may be different for different frames. For example, context
can be done in such a way that in frame A, main
is excluded, and in frame B footer
is. The axe.utils.getFrameContexts
method will provide a list of frames that must be tested, and what context to test it with.
function runPartialRecursive(context, options = {}, win = window) {
const { axe, document } = win;
// Find all frames in context, and determine what context object to use in that frame
const frameContexts = axe.utils.getFrameContexts(context, options);
// Run the current context, in the current window.
const promiseResults = [axe.runPartial(context, options)];
// Loop over all frames in context
frameContexts.forEach(({ frameSelector, frameContext }) => {
// Find the window of the frame
const frame = axe.utils.shadowSelect(frameSelector);
const frameWin = frame.contentWindow;
// Call axe.runPartial() in the frame, and its child frame
const frameResults = runPartialRecursive(frameContext, options, frameWin);
promiseResults.push(...frameResults);
});
return promiseResults;
}
important: The order in which these methods are called matters for performance. Internally, axe-core constructs a flattened tree when axe.utils.getFrameContexts
is called. This is fairly slow, and so should not happen more than once per frame. When axe.runPartial
is called, that tree will be used if it still exists. Since this tree can get out of sync with the actual DOM, it is important to call axe.runPartial
immediately after axe.utils.getFrameContexts
.
To run efficiently, axe.runPartial
calls should happen in parallel, so that when possible browsers can test multiple frames simultaneously.
The axe.finishRun
method does two things: It calls the after
methods of checks that have them, and it creates the final report. For axe.finishRun
to work, the order in which the partial results are passed to it is important. Starting from the top window, partial results must include child frame content before sibling frames. For example:
// Given the following frame structure:
// top
// - frame_1
// - frame_1a
// - frame_2
// The partial results are passed in the following order:
axe.finishRun([top, frame_1, frame_1a, frame_2]);
If for some reason axe.runPartial
fails to run, the integration API must include null
in the data in place of the results object, so that axe-core knows to skip over it. If a frame fails to run, results from any descending frames must be omitted. To illustrate this, consider the following:
// Given the following frame structure:
// top
// - frame_1
// - frame_1a
// - frame_2
// If axe.runPartial throws an error, the results must be passed to finishRun like this:
axe.finishRun([top, null, /* nothing for frame 1a, */ frame_2]);
important: Since axe.finishRun
may have access to cross-origin information, it should only be called in an environment that is known not to have third-party scripts. When using a browser driver, this can for example by done in a blank page.
The axe.utils.getFrameContexts
method takes any valid context, and returns an array of objects. Each object represents a frame that is in the context. The object has the following properties:
frameSelector
: This is a CSS selector, or array of CSS selectors in case of nodes in a shadow DOM tree to locate the frame element to be tested.frameContext
: This is an object is a context object that should be tested in the particular frame.
The options
object takes the same RunOptions object that axe.run accepts. When the iframes
property is false
, it returns an empty array.
Because axe.finishRun
does not run inside the page, the reporter
and after
methods do not have access to the top-level window
and document
objects, and might not have access to common browser APIs. Axe-core reporter use the environmentData
property that is set on the partialResult object of the initiator.
Because of this constraint, custom reporters, and custom rulesets that add after
methods must not rely on browser APIs or globals. Any data needed for either should either be taken from the environmentData
property, or collected in an evaluate
method of a check, and stored using its .data()
method.
When building integrations with browser drivers using axe-core, it is safer and more stable to use axe.runPartial
and axe.finishRun
then to use axe.run
. These two methods ensure that no information from one frame is ever handed off to another. That way if any script in a frame interferes with the axe
object, or with window.postMessage
, other frames will not be affected.
Because axe.runPartial
does not require frame communication, it can run in any frame, regardless of its origin. This is different from axe.run()
, which is limited by axe.configure({ allowedOrigins })
.