Using Dynamic Selectors
Configure Axe Watcher to properly track accessibility issues on pages with dynamically generated IDs and classes
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.
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-iframeThis 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
When these identifiers change between test runs, 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-iframeWill 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 > iframeThis 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.
See Also
- API Reference for JavaScript and TypeScript Complete documentation for
runOptionsand other configuration options AxeRunOptionsClass Java API reference for runtime options- Glossary: Duplicate Understanding how Axe Developer Hub identifies duplicate issues
