skip to content

You add an automated axe check to component tests running in a simulated DOM. A test that renders a lone button fails with "all page content should be contained by landmarks", and colour-contrast problems are never reported at all. Explain both results and how you would configure the check.

level: seniorimportance: should knowfreq 33%

answer

  1. fragment is not a page
  2. tags decide policy, not case by case
  3. no layout means no colours to compare
  4. undecided is not a pass
  5. move the check to where pixels exist

basics

~20 s

Both come from asserting outside what the environment can decide. Page-level rules like landmark containment are meaningless over a rendered fragment, and a simulated DOM does no layout or painting, so contrast rules return undecided results rather than failures. Scope the rule set; check contrast in a browser.

solid answer

~50 s

The landmark failure is a scope mismatch: rules such as landmark containment, single main region, page language and heading structure evaluate a *document*, and a component test renders a fragment into a bare container that was never meant to be a page. Many of those rules are also tagged best-practice rather than a conformance failure. The contrast silence is an environment limit: a simulated DOM performs no layout and no painting, so the engine cannot resolve the rendered foreground and background colours, and the rule lands in the `incomplete` bucket — which the matcher does not fail on. So I configure the run to the rules that are meaningful at this scope, usually with `runOnly` restricted to conformance tags, plus a small explicit disable list with a written reason for each entry, applied once in a shared helper. Contrast and other layout-dependent checks move to a real browser or to reviewing the design tokens themselves.

code

javascript · 12 lines
javascript
import { configureAxe } from 'jest-axe';

// Component tests render fragments in a non-rendering DOM.
// Assert only what this scope and environment can decide.
export const axe = configureAxe({
  runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] },
  rules: {
    // Page-level: checked against assembled routes instead.
    region: { enabled: false },
    'landmark-one-main': { enabled: false },
  },
});

go deeper

for a junior

Know that a component test renders a fragment, not a page, so rules about page structure will complain about things the component was never responsible for.

for a middle

Explain both mechanisms: page-level rules are out of scope over a fragment, and layout-dependent rules cannot resolve rendered values without layout or paint, so they return undecided results that never fail the test.

for a senior

Show that you configure the rule set deliberately — restricted by conformance tag in one shared helper, with each explicit disable carrying a reason — and that you relocate contrast and structure checks to environments that can actually decide them.

for a principal

Own the layering policy across the codebase: which rules are asserted at which level, how design tokens remove whole classes of defect before any test runs, and how you keep the disable list from becoming a place where regressions quietly live.

## Two different failures that look like one problem Both symptoms are versions of the same rule: only assert in an environment what that environment can actually decide. But they fail differently, and conflating them leads people to silence real defects. ## Symptom one — page-level rules on a fragment Accessibility rule sets are written for documents. A substantial fraction of the rules ask questions about the page as a whole: is all content inside a landmark region, is there exactly one main landmark, does the document declare a language, is there a mechanism to bypass repeated blocks, is the heading structure sane. A component test renders a fragment — one button, one card, one form field — into an otherwise empty container. There is no `<main>`, no `<html lang>`, no heading hierarchy, because none of those are the component's job. The rule is not wrong; it is being asked about a thing that does not exist at this scope. Answering it here produces noise, and noise is corrosive: it trains people to reach for the disable switch reflexively, which is how genuine failures get silenced later. There is a second wrinkle. Rules carry tags, and by default an unconfigured run includes best-practice rules alongside the ones that map to conformance criteria. Landmark containment is a best practice for a page, not a failure of a button. The fix is to narrow the rule set deliberately rather than case by case: ```js const results = await axe(container, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] }, rules: { region: { enabled: false } }, }); ``` Restricting by tag decides the *policy* once; the explicit `rules` entries handle the individual page-level rules that survive the tag filter. Every disabled rule deserves a comment saying why it is off and where that concern is covered instead — otherwise the disable list becomes an unexamined pile that hides regressions. ## Symptom two — rules that cannot run at all Contrast is different in kind. It is not out of scope for a component; it is undecidable in this environment. Evaluating contrast requires knowing the actual rendered foreground colour and the actual background behind it, which means resolving cascade, compositing and stacking — the product of layout and paint. A simulated DOM implements the tree and much of the API surface but does not lay out or paint anything, so those values do not exist. The engine handles this honestly: instead of guessing, it records the rule as **incomplete** — ran, could not decide, needs a human or a better environment. The `toHaveNoViolations` matcher only fails on the `violations` bucket, so incomplete entries pass straight through in silence. That is why nobody ever sees a contrast failure from these tests, and it is a trap if you assumed the suite covered it. Everything layout-dependent shares this fate: target size, overlapping controls, content that reflows out of reach at high zoom. The correct conclusion is not to force the rule on but to move the check somewhere pixels exist. ## Where those checks belong instead Contrast is usually better solved at the source than per component. The colours come from a small set of design tokens; validating the token palette once — every foreground/background pairing the system permits — covers every component that uses them, and it catches the problem at design time rather than at test time. What remains is the component that composes tokens in an unforeseen way, and that is caught by a real-browser pass over assembled pages. Page-level structure rules belong at page level too: a check run against an assembled route, in whatever environment renders full pages, is where landmark containment and heading structure are genuine and meaningful. ## How to keep the configuration honest Three habits keep this from rotting: 1. **One place.** Put the configured runner in a shared test helper — with jest-axe, `configureAxe` builds a pre-configured `axe` you import everywhere — so the rule policy is a single reviewable artefact rather than an accumulation of per-file options. 2. **Reasons, not just switches.** Every disabled rule carries a comment: out of scope at this level, covered by X, or known defect with a ticket. A disable with no reason is indistinguishable from a bug being hidden. 3. **Surface the incompletes when they matter.** For components where an undecided rule represents a real risk, log or assert on the incomplete entries so the gap is visible rather than implicit. At minimum, document that the component suite makes no claim about contrast. ## The general principle The interviewer is usually probing for one idea: a test asserting something its environment cannot observe is worse than no test, because it manufactures either false confidence or false alarms. Match the assertion to the scope and the capabilities of where it runs — fragment-level rules in component tests, document-level rules against assembled pages, pixel-level rules where pixels are actually produced.

  • Why is blanket-disabling the noisy rules riskier than restricting the run by tag?
    A disable list grows by accretion — each entry added under deadline pressure to make one test green, and never revisited. Restricting by conformance tag states a policy once, in one place, and keeps the remaining disables short enough to review. It also keeps the reason visible: "this level checks conformance rules" is a defensible sentence; a list of eleven rule ids is not.
  • How would you make the gap around contrast visible rather than implicit?
    Two ways, and I would do both. Document what the component suite claims — no contrast coverage — so nobody cites a green run as proof. And validate the palette itself: the tokens are a small finite set of foreground/background pairs, so checking them once covers every component and catches problems before a component exists.
  • Where would you run the page-level rules that you switched off in component tests?
    Against assembled routes, in an environment that renders real pages. That is where landmark containment, a single main region, document language and heading structure are genuine questions with genuine answers. The split is deliberate: fragment rules at the component level, document rules at the page level, nothing asserted where it cannot be observed.
  • A teammate proposes asserting that the incomplete array is empty. What do you say?
    That it inverts the meaning. Incomplete means the engine could not decide, which in a simulated DOM is the normal, expected state for every layout-dependent rule — so the assertion would fail everywhere and teach people to delete it. If a particular undecided rule represents real risk for a component, name that one rule and check it where it can actually be evaluated.

saying these in an interview costs you the question

  • Disables every noisy rule with no recorded reason
  • Assumes contrast is covered by the component suite
  • Thinks incomplete results are the same as passes
  • Runs page-level rules against isolated component fragments
  • Forces layout-dependent rules on in a simulated DOM

context