Skip to content

Latest commit

 

History

History
95 lines (68 loc) · 6.23 KB

run-partial.md

File metadata and controls

95 lines (68 loc) · 6.23 KB

axe runPartial / finishRun

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.

axe.runPartial(context, options): Promise

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);
  // 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.

axe.finishRun(partialResults, options): Promise

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.

axe.utils.getFrameContexts(context): FrameContext[]

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.

Custom Rulesets and Reporters

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.

Recommendations

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 }).