Using Dynamic Selectors

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

Configure Axe Watcher to properly track accessibility issues on pages with dynamically generated IDs, classes, and URLs

Not for use with personal data

When testing pages that generate dynamic element IDs or class names on each page load, Axe Watcher may have difficulty tracking whether accessibility issues are duplicates across test runs. This article explains how to configure Watcher to handle these scenarios correctly. A dynamic URL causes the same symptom for a different reason and needs a different fix, covered in Dynamic URLs below.

The Problem with Dynamic Selectors

By default, Axe Watcher uses CSS selectors that include element IDs and classes to identify where accessibility issues occur. For example, an issue might be reported at:

iframe#main-iframe

This approach works well when your page's IDs and classes remain consistent between page loads. However, many modern web applications generate dynamic identifiers that change on every page render, such as:

  • #component-a1b2c3d4
  • .form-field-xyz789

Axe Watcher also sends an XPath for each element, and Axe Developer Hub treats a match on either selector as the same element. Because an XPath never includes class names, a page whose class names alone are dynamic is often tracked correctly without any further configuration. An XPath does include an element's ID when that ID is unique on the page, though, producing a path such as //div[@id='component-a1b2c3d4']. A dynamic ID therefore changes the CSS selector and the XPath together, leaving nothing stable to match on.

When identifiers change between test runs this way, Axe Watcher cannot determine whether an issue is a duplicate of a previously detected issue or a new occurrence. This can result in:

  • The same issue being reported as both "new" and "resolved" on each test run
  • Inaccurate tracking of your accessibility progress over time
  • Difficulty identifying which issues have genuinely been fixed

The Solution: Enable Ancestry Tracking

To handle dynamic selectors, set the ancestry option to true in your runOptions configuration. When enabled, Axe Watcher uses the element's position within the DOM tree rather than relying on IDs and classes to locate elements between test runs.

With ancestry enabled, a selector that previously looked like this:

iframe#main-iframe

Will instead include the complete path from the root element:

html > body > div:nth-child(20) > div:nth-child(1) > div > div > ul > li:nth-child(1) > div > span > iframe

This positional selector remains consistent across page loads, even when IDs and classes change, allowing Axe Watcher to accurately track duplicate issues.

Configuration Examples

JavaScript and TypeScript

Add the ancestry option to your axe configuration's runOptions:

const config = {
  axe: {
    apiKey: process.env.ACCESSIBILITY_API_KEY,
    projectId: process.env.PROJECT_ID,
    runOptions: {
      ancestry: true
    }
  }
}

Java

Use the setAncestry() method on your AxeRunOptions object:

AxeRunOptions runOptions = new AxeRunOptions()
    .setAncestry(true);

AxeWatcherOptions options = new AxeWatcherOptions()
    .setApiKey(System.getenv("ACCESSIBILITY_API_KEY"))
    .setProjectId(System.getenv("PROJECT_ID"))
    .setRunOptions(runOptions);

AxeWatcher watcher = new AxeWatcher(options);

When to Use Ancestry Tracking

Enable ancestry: true when your application:

  • Uses frameworks that generate dynamic component IDs (React, Vue, Angular)
  • Employs CSS-in-JS libraries that generate unique class names
  • Has form fields or interactive elements with auto-generated identifiers
  • Shows inconsistent "new issue" and "resolved issue" counts between test runs for what appear to be the same issues

Trade-offs to Consider

While ancestry tracking solves the dynamic selector problem, there are some considerations:

  • Selector readability: Positional selectors are longer and can be harder to read when reviewing issues in Axe Developer Hub.
  • DOM structure sensitivity: If your page's DOM structure changes significantly between renders (not just the IDs/classes), the positional selectors may also change.
  • Debugging: When investigating an issue, you may find it easier to locate an element by a meaningful ID than by its position in the DOM tree.

For most applications with dynamic identifiers, the benefits of accurate issue tracking outweigh these trade-offs.

Dynamic URLs

Element selectors are only part of how Axe Developer Hub identifies an issue. The URL of the page is compared as well, in full, including any query string. Enabling ancestry has no effect on this comparison.

A URL that varies between test runs therefore produces the same symptoms as a dynamic selector, and does so more aggressively: when a URL hasn't been seen before, every issue found on it is reported as new, without the element selectors being consulted at all. Common causes include:

  • Session IDs or authentication tokens carried in the query string
  • Timestamps or cache-busting parameters, such as ?v=1738012800
  • Randomized IDs in the path, such as /orders/8f3c1b2a/summary
  • Test fixtures that generate a fresh record, and therefore a fresh URL, on each run

There is no configuration option that normalizes URLs before they are compared, so this has to be solved in your test suite: navigate to deterministic URLs, so that the same page produces the same URL on every run. In practice this usually means seeding fixed test data instead of generating new records per run, and removing or pinning volatile query parameters before the page is scanned.

If a URL genuinely cannot be stabilized, you can keep it out of your results entirely with excludeUrlPatterns, though this means the page isn't scanned at all rather than tracked correctly. See Exclude URLs from Analysis for details.

See Also