Analyze Cross-Origin iframes

This page is not available in the language you requested. You have been redirected to the English version of the page.
Link to this page copied to clipboard

How to opt in to analyzing the content of cross-origin iframes with the allowedOrigins option

Not for use with personal data

Axe Watcher analyzes the content of same-origin <iframe> elements along with the rest of the page. Frames served from a different origin are skipped by default, and accessibility issues inside them are left out of your results entirely.

The allowedOrigins option opts in to analyzing those frames. You name the origins you want covered, and Watcher analyzes frames served from them as part of each analysis of the embedding page.

This option requires Watcher 4.6.0 or later, and is available with the JavaScript/TypeScript integrations and the Java integrations.

When You Need This

Set allowedOrigins when meaningful parts of your user experience are served from another origin, such as a hosted payment form, an embedded booking or scheduling widget, a media player with its own controls, or a help or chat widget.

You don't need it for frames served from your own origin, which are always analyzed.

Configure Allowed Origins

List only the embedded origins you want covered. Your own application's origin is always allowed, so don't include it.

JavaScript and TypeScript

axe: {
  apiKey: process.env.AXE_DEVELOPER_HUB_API_KEY,
  projectId: process.env.AXE_PROJECT_ID,
  allowedOrigins: [ 'https://pay.example.com' ]
}

Java

AxeWatcherOptions options = new AxeWatcherOptions()
    .setApiKey(System.getenv("ACCESSIBILITY_API_KEY"))
    .setProjectId(System.getenv("PROJECT_ID"))
    .setAllowedOrigins(new String[] {"https://pay.example.com"});
AxeWatcher watcher = new AxeWatcher(options);

Decide Which Origins to Trust

important

Listing an origin lets that frame exchange page markup with the frame that directly embeds it. List only origins you trust with the content of the page under test.

The allowlist is applied in every frame, not just the top one, and it works in both directions: a frame accepts a message only from an origin in its own list, and replies only to those origins. In practice each frame allows its own origin, every origin you list, and, only when that frame's own origin is one you listed, the origin of the frame that directly embeds it, provided that embedder is either the top-level page or another origin you listed.

So listing an origin doesn't authorize it to answer whatever page happens to frame it. What it does authorize is the exchange of markup between that frame and its embedder within the page under test, which is why the list should stay scoped to embeds you trust.

This is also why wildcards aren't supported and why there's no option meaning "analyze every frame." An allowlist you can't enumerate isn't an allowlist. Name each origin explicitly, and keep the list to the embeds you actually need covered.

Consider whether the page under test displays anything sensitive while your tests run. If your test fixtures use realistic personal or payment data, weigh that against the third-party origins you're about to allow.

Write the Origins Correctly

Each entry must be an origin and nothing else: a scheme (http or https), a host, and an optional port. Watcher normalizes what you provide by removing a trailing slash and a default port (:80 for http, :443 for https), lowercasing the host, and discarding duplicates.

Watcher rejects an entry it can't use by reporting an error when your configuration is read, rather than letting the test run continue and analyze nothing. In Java, setAllowedOrigins() throws IllegalArgumentException.

Entry Result
https://pay.example.com Valid
https://pay.example.com:8443 Valid
https://*.example.com Error. Wildcards aren't supported; name every origin explicitly
https://pay.example.com/checkout Error. A path, query, fragment, or credentials aren't allowed
pay.example.com Error. The scheme is required
ftp://pay.example.com Error. Only http and https origins can be analyzed
https://café.example.com Error. Use the punycode form of the domain

Domains That Contain Non-English Characters

A domain containing characters outside the English alphabet, such as café.example.com or пример.рф, has a second, equivalent spelling made up only of the letters a to z, the digits 0 to 9, and hyphens. That spelling is called punycode, and it always begins with xn--. Browsers convert a domain to its punycode form before using it, so punycode is the form Watcher compares against.

Domain Punycode form to use
café.example.com xn--caf-dma.example.com
пример.рф xn--e1afmkfd.xn--p1ai

To find the punycode form, visit the frame's URL and read your browser's address bar after the page loads, or use any online punycode converter.

Don't substitute a similar-looking English spelling, such as cafe.example.com for café.example.com. Watcher accepts it, because it's a valid origin, but it never matches the frame's real origin, so the frame is silently left unanalyzed.

The <same_origin> and <unsafe_all_origins> Keywords

The accessibility engine Watcher uses, axe-core, accepts two keywords in its own equivalent setting, and you may encounter them in axe-core documentation or in a configuration you're migrating:

  • <same_origin> means "this page's own origin." Watcher accepts it, but it has no effect, because your own origin is always allowed.
  • <unsafe_all_origins> means "every origin, including ones you haven't listed." Watcher rejects it with an error, for the reasons described in Decide Which Origins to Trust.

Capture Changes Made Inside a Frame

Automatic analysis detects changes to the top-level page. It can't detect a change made inside a frame, whether that frame is same-origin or cross-origin. An allowed frame is therefore analyzed as of the last change to the top-level page.

This matters when you interact with content inside a frame. The interaction changes the frame's content, the top-level page is unchanged, and so no automatic analysis runs:

// Interacting inside the frame doesn't trigger an automatic analysis
await page.frameLocator('#pay').getByRole('button', { name: 'Continue' }).click()

// Analyze explicitly to capture the resulting state
await controller.analyze()

Call analyze() after the interaction to capture the resulting state. An analysis you request explicitly always runs. See Control Your Scans for how to obtain a controller object in your test framework.

Find the Frames You're Missing

Watcher reports the cross-origin frames it skipped, whether or not you've set allowedOrigins. This means you can discover which embeds are missing from your results before you configure anything.

The cross_origin_frame_not_allowlisted diagnostic names each skipped origin and includes the allowedOrigins line to paste into your configuration. On a page with many third-party embeds the list is capped, and the rest is summarized as "and N more."

Diagnostics appear only in debug output, and each one is reported at most once per test run.

JavaScript and TypeScript: set the DEBUG environment variable when you run your tests.

DEBUG=axe-watcher:* npx playwright test

Java: the controllers log each diagnostic at DEBUG level, so configure your logging framework to show DEBUG messages for Axe Watcher. With the Selenium integration, you can also call enableDebugLogger() on AxeWatcher.

Two other diagnostics report frames that were allowed but still not analyzed. Both are described under Limitations:

  • cross_origin_frame_unscannable, for a frame sandboxed without allow-same-origin.
  • cross_origin_frame_timed_out, for a frame that didn't return results in time.

If you'd rather have frame coverage show up in your results than in debug output, enable best practices. The axe-core frame-tested rule distinguishes "no issues in this frame" from "this frame was never analyzed": a frame Watcher couldn't reach is reported as needing review. Because it's a best-practice rule, a ruleset limited to WCAG rules omits it.

Issues found inside a frame are attributed to the page state of the page that embeds the frame, not to a separate page state of their own.

Limitations

The limitation you're most likely to hit is that automatic analysis can't see changes made inside a frame, described under Capture Changes Made Inside a Frame. The rest are below.

A Sandboxed Frame Needs allow-same-origin

A frame whose sandbox attribute omits allow-same-origin has an opaque origin, which no allowlist entry can name, so it can't be analyzed no matter what you list. Once you list its origin, Watcher reports it with the cross_origin_frame_unscannable diagnostic. Until you do, it's reported as cross_origin_frame_not_allowlisted like any other skipped frame. Add allow-same-origin to the sandbox attribute to analyze the frame.

Only allow-same-origin matters here. A sandboxed frame that omits allow-scripts is still analyzed normally, because Watcher's content script runs in an isolated world and executes even where the page's own scripts are blocked.

Embeds That Redirect

An embed whose src redirects to another origin, such as an apex domain redirecting to www or http redirecting to https, ends up on an origin that isn't the one you listed, so it isn't analyzed. List the origin the frame actually ends up on rather than the one in its src.

Frames Nested More Than One Level Deep Aren't Reported

Frame coverage diagnostics are reported from the top-level page, about the frames it embeds directly. A frame that couldn't be reached but is nested two or more levels deep won't appear in your debug output, even though its contents are missing from the results.

A Frame That Never Responds Fails the Analysis

A frame that answers Watcher's initial ping but never returns results fails the analysis outright rather than under-reporting it, and Watcher reports the cross_origin_frame_timed_out diagnostic. If a slow third-party embed does this, either remove its origin from allowedOrigins or raise runOptions.frameWaitTime.

Budget for Slower Analysis

Every origin you allow adds that frame's whole content to each analysis of the page. The frame's content is collected, transferred to the top-level page, and combined with the rest of the results, and all of that happens while your test waits.

How much time this adds grows with the size and complexity of the framed content, and with the number of frames you allow. With automatic analysis enabled, the cost recurs on every interaction Watcher analyzes, so a long test suite with several allowed frames can add a substantial amount of time to a run. Two ways to keep it manageable:

  • Scope allowedOrigins to the embeds you actually need covered, rather than every third-party origin on the page.
  • Consider enabling it per project or per test suite, so suites that don't exercise framed content don't pay for it.

If you want to measure the effect on your own suite, time a representative run before and after enabling the option.

Timeouts

Because analyzing a cross-origin frame includes waiting for that frame to respond, the default analyze and flush timeouts change from 5000ms to 10000ms when allowedOrigins is set to a non-empty array. The start and stop defaults are unchanged. Timeout values you set yourself are always used as given, so this affects only the defaults. See Set Timeouts.

This applies to the JavaScript and TypeScript integrations. Java Watcher doesn't currently support custom timeouts, and its timeout values aren't changed by this option.

If you set your own timeouts and then enable allowedOrigins, review them. A value tuned for a page without framed content may now be too tight.