PRACTICAL GUIDE / Playwright reduced motion testing

Test Reduced Motion with Playwright

Use Playwright reduced motion testing with media emulation to verify static alternatives, disabled animations, usable content, and regression checks in CI.

By The Testing AcademyUpdated July 25, 202620 min read
All field guides
In this guide11 sections
  1. What Should Playwright Reduced Motion Testing Prove?
  2. How Does prefers-reduced-motion Reach Application Code?
  3. Reduced Motion, No Animation, and Essential Motion: What Is the Difference?
  4. A Numbered Workflow for Testing Both Motion Modes
  5. Code Example: Emulate Reduced Motion Before Page Load
  6. How Do You Assert Behavior Without Waiting on Animation Timing?
  7. Code Example: Test ArticleDiagram in Default and Reduced Modes
  8. Which Reduced-Motion Failures Matter Most?
  9. How Should Reduced Motion Tests Fit Accessibility and Visual CI?
  10. Frequently Asked Questions About Playwright Reduced Motion Tests
  11. How do I emulate prefers-reduced-motion in Playwright?
  12. Should reduced motion remove every animation?
  13. Do I need to reload after changing reducedMotion?
  14. How can I test CSS and JavaScript motion branches?
  15. Should I use screenshots for reduced-motion testing?
  16. What should remain usable when animation is reduced?
  17. Next Steps: Put Both Motion Modes Into CI

What you will learn

  • What Should Playwright Reduced Motion Testing Prove?
  • How Does prefers-reduced-motion Reach Application Code?
  • Reduced Motion, No Animation, and Essential Motion: What Is the Difference?
  • A Numbered Workflow for Testing Both Motion Modes

Playwright reduced motion testing starts with page.emulateMedia({ reducedMotion: "reduce" }), which changes the page's prefers-reduced-motion result. The real test comes next: verify that content stays visible, nonessential transitions do not arm or run, controls still work, and layout remains stable. A test that only checks matchMedia() proves browser emulation, not the accessible behavior delivered by the application.

QABattle provides a concrete contract. ArticleDiagram checks the media preference during its effect and returns before setting data-motion="armed" when reduction is requested. Its content is still rendered as a figure, ordered flow, labeled nodes, and accessible description. The existing end-to-end test proves the default animation path first, then reloads under reduced motion and checks that the armed state is absent.

What Should Playwright Reduced Motion Testing Prove?

A complete motion test proves five things. The browser is in the intended preference mode. The component takes its documented reduced branch. Essential content remains present and readable. Controls and focus behavior still complete the task. The page does not gain clipping, overflow, or a broken layout. A default-mode control case also proves that the feature still animates when motion is allowed.

Those claims matter because "reduce" is a preference, not a command to hide content. Replacing an animated diagram with an empty gap would technically stop its motion while making the page less usable. Disabling a loading indicator without providing a static status would remove feedback. Freezing a carousel over a focused control could block interaction.

Playwright's page.emulateMedia() API supports a reducedMotion media feature, as documented in the official page emulation reference. Use it to establish the browser condition. Then assert product behavior with locators, attributes, accessible names, bounding boxes, and task completion.

This technique fits within a wider accessibility testing checklist, but its scope is intentionally narrow. Automated axe scans can detect many rule violations, yet they do not generally know whether a custom animation provides the promised reduced alternative. The axe automation guide complements this behavioral test rather than replacing it.

The strongest release evidence uses two modes:

  • Default mode proves the diagram reaches its intended visible animated state.
  • Reduced mode proves the same information is available without arming that animation.
  • Both modes prove node count, labels, dimensions, and layout remain usable.

Without the default case, a permanently disabled animation can make the reduced test pass. Without the reduced case, the ordinary feature test says nothing about the user's preference. The paired design catches regressions in both directions.

How Does prefers-reduced-motion Reach Application Code?

The signal travels through four layers. A user chooses a motion preference in the operating system. The browser exposes that choice through the prefers-reduced-motion media feature. CSS can react with a media query, and JavaScript can query it through window.matchMedia. The application then selects a lower-motion behavior.

The W3C Media Queries Level 5 specification defines the media feature and its no-preference and reduce values. Playwright changes the browser-level result, so both CSS media rules and matchMedia("(prefers-reduced-motion: reduce)") can observe the emulated condition.

In src/components/blog/article-diagram.tsx, the ArticleDiagram component creates figureRef with useRef<HTMLElement>(null). Its useEffect reads figureRef.current and evaluates:

window.matchMedia("(prefers-reduced-motion: reduce)").matches

If the figure is missing or the query matches, the effect returns. It does not assign the armed state and does not create an IntersectionObserver. In ordinary mode, it sets figure.dataset.motion = "armed", observes the figure, and changes the value to "visible" once the figure intersects.

The render path is independent of that branch. The figure, caption, ordered list, nodes, and accessible label are created in both modes. That separation supplies a strong test oracle: reduced motion changes enhancement state, not information availability.

The existing case in e2e/public.spec.ts first scrolls the diagram into view and expects data-motion="visible". It checks the accessible label, five list items, five rendered node articles, positive node dimensions, and no mobile overflow. It then calls page.emulateMedia({ reducedMotion: "reduce" }), reloads, and expects the diagram to remain visible without data-motion="armed".

For more coverage of accessible page structure, Playwright ARIA snapshots can capture stable semantic relationships. Keep the motion state assertion separate, since an accessibility tree snapshot does not prove whether animation code armed.

Reduced Motion, No Animation, and Essential Motion: What Is the Difference?

Teams often write one rule, "turn off all animation," then discover that the UI lost status or orientation cues. Classify motion by user value before deciding the assertion.

Motion categoryUser valueReduced alternativeAssertion targetRisk if mishandled
Decorative entrancevisual polishcontent starts visibleno armed entrance statedelayed or hidden content
Spatial transitionshows where content movedinstant change or short fadefinal state and focusdisorientation
Progress feedbackcommunicates ongoing workstatic text or restrained indicatorstatus remains perceivableuncertain task state
Error attentiondraws notice to a problempersistent text, icon, and focuserror name and focusmissed failure
Auto-updating motionpresents changing contentpause, stop, or static viewuser control and stable statedistraction
Essential demonstrationconveys information through motionequivalent explanation where possiblesame task meaninglost information

W3C's explanation of WCAG 2.3.3 Animation from Interactions discusses disabling motion animation triggered by interaction unless it is essential to functionality or information. The Pause, Stop, Hide explanation addresses moving, blinking, scrolling, and auto-updating information under its stated conditions.

The conformance levels and triggers are different. SC 2.3.3 is Level AAA and applies to nonessential motion animation triggered by interaction. SC 2.2.2 is Level A; its moving, blinking, or scrolling requirement applies when content starts automatically, lasts more than five seconds, and appears in parallel with other content, while its auto-updating branch has its own conditions. The broader testing matrix here is product policy, not a claim that every row represents the same WCAG requirement.

These references help define risk, but a repository test still needs a product-specific contract. For ArticleDiagram, the motion is a progressive entrance enhancement. The static nodes carry the same content, so skipping the observer and animated state is an appropriate reduced behavior.

Do not equate opacity zero with "not animating." A CSS rule can leave content invisible when the JavaScript that normally reveals it does not run. Assert visibility and nonzero dimensions. Likewise, do not remove a focus ring transition by removing the focus ring itself. Test the final perceivable state, not only a CSS duration.

The manual accessibility testing guide remains valuable for comfort, comprehension, and real settings. Automation can prove selected invariants; it cannot judge every experience of motion or vestibular sensitivity.

A Numbered Workflow for Testing Both Motion Modes

Use this sequence for each animated feature:

  1. Identify the feature's motion contract and the essential information it conveys.
  2. Load the page in default mode and bring the feature into its activation condition.
  3. Prove the ordinary path reaches its expected state without relying on a fixed sleep.
  4. Record content count, accessible name, control state, and layout invariants.
  5. Emulate reducedMotion: "reduce" before page startup whenever possible.
  6. Reload if the application reads matchMedia() only at mount or effect startup.
  7. Prove the reduced branch does not arm nonessential motion and still exposes the content.
  8. Exercise controls, keyboard focus, status updates, and navigation in reduced mode.
  9. Check mobile and desktop geometry for clipping, overflow, or collapsed content.
  10. Use a focused screenshot only when the static visual arrangement adds useful evidence.

Avoid waitForTimeout calls based on transition duration. A sleep makes tests slower and still races under load or changed CSS. Prefer a state contract such as data-motion, a final class, an event-driven status, or a stable computed style. The click timeout and auto-waiting guide explains why animation and overlays can affect actionability, but a reduced-motion test should assert its own behavior directly.

Define what absence means. QABattle's reduced contract is not specifically data-motion="static"; it is the absence of the armed state because the effect returns early. A test that invents a "static" value would fail against correct repository code. Evidence from the implementation decides the oracle.

The order also guards against false confidence. Verify that the same fixture really animates by default before claiming the reduced setting changed it. Verify content before and after so an empty component cannot pass. Verify the preference itself only as a diagnostic, not as the final outcome.

Code Example: Emulate Reduced Motion Before Page Load

Set the preference before navigation when the component reads it during startup. This example names the repository's ArticleDiagram contract and verifies browser state plus user-facing results.

TypeScript
import { expect, test } from "@playwright/test";

type MotionAuditWindow = Window & {
  __articleDiagramMotionStates?: Array<string | null>;
};

test("ArticleDiagram renders a static readable flow under reduced motion", async ({
  page,
}) => {
  await page.addInitScript(() => {
    const motionWindow = window as MotionAuditWindow;
    const motionStates: Array<string | null> = [];
    motionWindow.__articleDiagramMotionStates = motionStates;

    new MutationObserver((records) => {
      for (const record of records) {
        if (
          !(record.target instanceof Element) ||
          !record.target.matches('[data-testid="article-diagram"]')
        ) {
          continue;
        }
        motionStates.push(
          record.oldValue,
          record.target.getAttribute("data-motion"),
        );
      }
    }).observe(document, {
      subtree: true,
      attributes: true,
      attributeOldValue: true,
      attributeFilter: ["data-motion"],
    });
  });

  await page.emulateMedia({ reducedMotion: "reduce" });
  await page.goto(
    "/blog/playwright-test-agents-planner-generator-and-healer-workflow",
  );

  expect(
    await page.evaluate(() =>
      window.matchMedia("(prefers-reduced-motion: reduce)").matches,
    ),
  ).toBe(true);

  const diagram = page.getByTestId("article-diagram");
  await expect(diagram).toBeVisible();
  await expect(diagram).toHaveAttribute(
    "aria-label",
    /Planner, Generator, and Healer Workflow Field Map/,
  );

  const nodes = diagram.locator("article");
  await expect(nodes).toHaveCount(5);
  await expect(nodes.first()).toBeVisible();

  await page.evaluate(
    () =>
      new Promise<void>((resolve) =>
        requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
      ),
  );
  expect(await diagram.getAttribute("data-motion")).toBeNull();
  const observedStates = await page.evaluate(
    () => (window as MotionAuditWindow).__articleDiagramMotionStates ?? [],
  );
  expect(observedStates).toEqual([]);
});

The matchMedia check confirms test setup. The label, count, visibility, and final null attribute confirm the static result. The observer starts before application scripts, records old and current attribute values, and catches a transient armed state even if it quickly becomes visible. An empty audit proves no data-motion mutation appeared during the observed startup window.

Setting emulation before page.goto() is the clearest default because CSS and startup JavaScript see the preference from the beginning. It avoids a flash of default animation and covers code that reads once. Use this style when authoring a dedicated reduced-motion project or test.

The repository's current flow changes the preference after default and mobile checks, then reloads. That is also valid for this component because reload remounts ArticleDiagram, reruns its effect, and reaches the early return. The choice depends on whether one test intentionally compares modes or each mode has its own fixture.

For test-generation review patterns around accessibility, see Playwright accessibility automation interview scenarios and agent-generated accessibility tests. Generated checks still need the component-specific behavior contract used here.

How Do You Assert Behavior Without Waiting on Animation Timing?

Treat animation state as a state machine, not a stopwatch. QABattle exposes armed before intersection and visible after intersection. The default test scrolls the figure into view and waits with toHaveAttribute("data-motion", "visible"), an auto-retrying assertion. It does not guess how many milliseconds the transition needs.

In reduced mode, assert the exact static contract rather than only checking that the current value differs from armed. For ArticleDiagram, the final attribute must be absent. If the claim is that armed never appeared during startup, install a mutation observer before navigation and inspect both old and current values. Pair that evidence with positive content assertions because an element that never rendered also has no motion attribute.

Useful stable signals include:

  • A data attribute set by application state.
  • A class that selects the intended final mode.
  • Visible nodes and accessible labels.
  • Exact node or item counts from the fixture.
  • Enabled controls and preserved focus after activation.
  • Positive bounding-box dimensions.
  • Document width that does not exceed the viewport.
  • A status message that conveys essential progress without movement.

Computed CSS can be useful when CSS owns the contract, but avoid asserting every transition property. A test that pins 0.01s will break on harmless tuning. Assert that a nonessential transform is absent, duration is effectively none according to project policy, or the content begins in its final visible position.

Screenshots can catch a static layout regression, but they cannot prove that the page did not animate before capture. The visual accessibility scenarios guide and debugging visual snapshot differences help with image evidence. Keep behavioral state as the release gate.

For live preference changes, only write an immediate-switch test if the application subscribes to media query changes. ArticleDiagram reads once in an effect and does not register a change listener, so the evidence-backed path is reload after emulation. A test must not demand behavior the implementation never promised.

Code Example: Test ArticleDiagram in Default and Reduced Modes

This example is derived from the field-map case in e2e/public.spec.ts. It uses the data-testid="article-diagram" contract, validates the default path, then executes the branch controlled by figureRef and matchMedia.

TypeScript
import { expect, test } from "@playwright/test";

type MotionAuditWindow = Window & {
  __articleDiagramMotionStates?: Array<string | null>;
};

test("ArticleDiagram preserves content in both motion modes", async ({ page }) => {
  const route =
    "/blog/playwright-test-agents-planner-generator-and-healer-workflow";

  await page.addInitScript(() => {
    const motionWindow = window as MotionAuditWindow;
    const motionStates: Array<string | null> = [];
    motionWindow.__articleDiagramMotionStates = motionStates;

    new MutationObserver((records) => {
      for (const record of records) {
        if (
          !(record.target instanceof Element) ||
          !record.target.matches('[data-testid="article-diagram"]')
        ) {
          continue;
        }
        motionStates.push(
          record.oldValue,
          record.target.getAttribute("data-motion"),
        );
      }
    }).observe(document, {
      subtree: true,
      attributes: true,
      attributeOldValue: true,
      attributeFilter: ["data-motion"],
    });
  });

  await page.goto(route);
  const diagram = page.getByTestId("article-diagram");
  await diagram.scrollIntoViewIfNeeded();

  await expect(diagram).toHaveAttribute("data-motion", "visible");
  const nodes = diagram.locator("article");
  await expect(nodes).toHaveCount(5);

  const defaultBoxes = await nodes.evaluateAll((items) =>
    items.map((item) => {
      const box = item.getBoundingClientRect();
      return { width: box.width, height: box.height };
    }),
  );
  expect(
    defaultBoxes.every((box) => box.width > 80 && box.height > 80),
  ).toBe(true);

  await page.emulateMedia({ reducedMotion: "reduce" });
  await page.reload();

  await expect(diagram).toBeVisible();
  await expect(diagram.locator("article")).toHaveCount(5);

  const layout = await page.evaluate(() => ({
    documentWidth: document.documentElement.scrollWidth,
    viewportWidth: window.innerWidth,
  }));
  expect(layout.documentWidth).toBeLessThanOrEqual(layout.viewportWidth + 1);

  await page.evaluate(
    () =>
      new Promise<void>((resolve) =>
        requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
      ),
  );
  expect(await diagram.getAttribute("data-motion")).toBeNull();
  const reducedStates = await page.evaluate(
    () => (window as MotionAuditWindow).__articleDiagramMotionStates ?? [],
  );
  expect(reducedStates).toEqual([]);
});

Playwright locators are evaluated against the current document, so the diagram locator remains usable after reload. The init script also runs in the new document and resets the audit, separating default-mode mutations from reduced-mode evidence. The test repeats the node count because preservation across modes is part of the claim. The overflow assertion checks a page-level usability risk that can appear when a static state no longer receives animated positioning styles.

The dimensions in this repository example come directly from its existing test. Do not copy 80 into another component without evidence. A text cursor, icon, or compact button needs a different geometric contract.

If the feature contains controls, add a focused reduced-mode interaction after reload. Click, keyboard activation, and focus placement should have the same result as default mode. Reduced motion can alter transitions, but it must not alter task completion.

Which Reduced-Motion Failures Matter Most?

Prioritize user impact rather than CSS differences. Hidden information, blocked controls, lost focus, and overflow are release failures. A decorative transition that remains slightly longer than design intended may still need correction, but it is easier to triage when the test report separates behavior from styling.

FailureReduced-mode evidenceDefault controlRelease response
content hiddennode absent, invisible, or zero-sizedvisible after activationblock
controls blockedaction cannot completeaction completesblock
animation still armedreduced state has data-motion="armed"default reaches visibleblock
focus lostactive element moves unexpectedlyexpected focus pathblock
layout overflowdocument wider than viewportordinary layout fitsblock
essential status removedno perceivable progress or resultstatus existsblock
default animation brokendefault never reaches visible statereduced content remainsfix feature path

Include the emulated media result in failure diagnostics. If matchMedia is false, setup failed. If it is true but the final attribute is present or the startup audit contains armed, application branching failed. If motion is skipped but nodes are hidden, styling or final-state initialization failed.

Automated results do not describe every person's comfort. Schedule manual review for new motion patterns, parallax, zoom, large spatial transitions, and interactions whose meaning depends on movement. The test suite should enforce agreed basics while leaving room for informed accessibility judgment.

Color and motion can combine in state feedback. The color contrast testing guide covers perceivability of the static alternative. Do not replace motion with a low-contrast color-only cue and call the requirement solved.

How Should Reduced Motion Tests Fit Accessibility and Visual CI?

Run a small behavioral matrix on every change to shared animation infrastructure: default desktop, reduced desktop, and reduced mobile. Add default mobile when responsive placement differs. Keep fixture data deterministic and choose a component with a stable content contract.

Behavioral assertions should own release decisions. They are quick, readable, and tied to user outcomes: state not armed, content visible, controls usable, focus preserved, no overflow. A focused screenshot can supplement them for a complex static arrangement. Avoid whole-page snapshots whose unrelated content creates noise.

Use separate test names or test steps for browser emulation, component state, interaction, and geometry. When CI fails, the report should say which layer broke. Preserve a Playwright trace for hard-to-reproduce interaction or layout failures, but do not rely on manually watching a video to decide every result.

Treat preference setup as test isolation. Reset media emulation or create a fresh page before a later case that expects no-preference; otherwise one reduced setting can leak through a shared context and make the control case misleading. If the suite uses projects, name the motion mode in the project and report so screenshots, traces, and retries cannot be confused. Keep device size and motion preference as separate declared dimensions unless their interaction is under test.

When a shared animation utility changes, select fixtures that exercise both JavaScript and CSS-owned behavior. Report the component's state marker, visible result, and media-query result together. A failure where emulation is correct but content is hidden belongs to application styling. A failure where the media query does not match belongs to test setup. That separation shortens accessibility triage and avoids weakening assertions after an environmental failure.

An accessibility scan still belongs in the pipeline. It can catch names, roles, contrast rules, and document issues outside motion. Manual review with the real operating-system setting remains valuable, especially for comfort and comprehension. These methods answer different questions.

The device, locale, and permission emulation guide shows how environmental settings can form Playwright projects. Reduced motion can follow the same pattern: one project supplies ordinary media, another supplies reduction, and shared outcome helpers run against both where appropriate.

Keep snapshot baselines mode-specific. A reduced screenshot should not be compared with a default screenshot captured at an arbitrary animation frame. Navigate, establish the mode before startup, wait for the stable state contract, then capture only the meaningful region.

Project-level emulation is useful when many features share the preference. Give the reduced project an explicit name and keep the default project as a control. Shared tests can then assert content and interaction in both projects, while component-specific tests inspect only the motion state that belongs to that feature. This arrangement prevents every test from repeating setup without assuming that all components use the same static alternative.

Review failures in outcome order. First confirm the emulated query matches. Next confirm content, controls, labels, and focus are present. Then inspect the armed state, geometry, and screenshot. A styling difference should not distract from hidden content or a blocked task. The same order makes CI reports useful to accessibility reviewers and component owners.

Recheck the real operating-system setting before shipping a new motion pattern. Browser emulation gives deterministic coverage, but a manual pass can reveal startup flashes, chained transitions, or discomfort that a state assertion does not measure. Record the intended alternative in the component contract so later tests and reviews evaluate the same behavior.

Frequently Asked Questions About Playwright Reduced Motion Tests

How do I emulate prefers-reduced-motion in Playwright?

Call page.emulateMedia({ reducedMotion: "reduce" }) before navigation for a clean startup condition. You can confirm setup with window.matchMedia, but continue to assert the lower-motion behavior, visible content, controls, and layout. Browser emulation is the input, not the user-facing result.

Should reduced motion remove every animation?

No. Remove or replace nonessential movement while preserving necessary feedback and meaning. A static label, short fade, or immediate state change may be appropriate depending on the feature. Document the intended alternative so tests check product behavior instead of applying a blanket duration rule.

Do I need to reload after changing reducedMotion?

Reload when the component reads the preference during mount. ArticleDiagram checks inside useEffect, and the repository test reloads after changing emulation so the branch runs again. Components that subscribe to media query changes can support a separate live-switch test without a reload.

How can I test CSS and JavaScript motion branches?

Use meaningful final-state styles for CSS and a stable application state signal for JavaScript. QABattle asserts that data-motion="armed" is absent in reduced mode. In both cases, pair implementation evidence with visibility, accessible naming, interaction, and layout checks.

Should I use screenshots for reduced-motion testing?

Use them for focused static layouts when a visual comparison adds value. Do not use them as the only proof, because a captured frame cannot show whether motion ran earlier or whether a control works. Establish a stable behavior state before capturing a mode-specific baseline.

What should remain usable when animation is reduced?

Content, controls, keyboard focus, navigation, errors, progress, and status feedback should still support the same task. The component must not disappear, overflow, trap input, or remove essential meaning. Reduced mode changes presentation of motion, not access to the feature.

Next Steps: Put Both Motion Modes Into CI

Start with the ArticleDiagram pair already proven by repository evidence. Keep the default assertion that scrolling reaches data-motion="visible". In reduced mode, require an absent final attribute and use startup mutation evidence before saying the component never armed. Preserve node count, accessible label, bounding-box, and mobile overflow checks.

Then inventory other motion features and classify each as decorative, spatial, status-related, auto-updating, or essential. Write a reduced alternative before writing selectors. Give JavaScript-driven motion a stable state contract and CSS-driven motion a meaningful final-state assertion.

The finished suite should answer more than whether the browser preference changed. It should prove that each selected feature respects that preference while preserving information, interaction, focus, and layout, and that its ordinary mode still works when motion is allowed.

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 25, 2026 / Reviewed July 25, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Official playwright.dev reference

    playwright.dev

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

FAQ / QUICK ANSWERS

Questions testers ask

How do I emulate prefers-reduced-motion in Playwright?

Call page.emulateMedia with reducedMotion set to reduce before navigating when possible. The page will then report a matching prefers-reduced-motion media feature during startup. Assert that result once, then verify the application's lower-motion behavior, visible content, interaction, and stable layout rather than stopping at emulation.

Should reduced motion remove every animation?

No. The preference asks applications to reduce nonessential motion, not erase all visual feedback. Preserve essential status, progress, and state-change communication in a less motion-heavy form. Write the expected alternative for each feature, then test that content and controls remain understandable while unnecessary movement is not armed.

Do I need to reload after changing reducedMotion?

Reload when application code reads matchMedia only during mount or startup. QABattle's ArticleDiagram checks the preference inside a useEffect, so its existing test changes reducedMotion and reloads to execute that branch again. If a component listens for media-query changes, a separate test can verify live switching without reload.

How can I test CSS and JavaScript motion branches?

For CSS, inspect meaningful computed styles or final visual state under both media settings. For JavaScript, expose a stable state contract such as QABattle's data-motion attribute and assert the reduced branch never arms. In both cases, also verify content visibility and interaction so implementation details do not become the only evidence.

Should I use screenshots for reduced-motion testing?

Use focused screenshots as supporting evidence for a stable static result, especially at desktop and mobile sizes. Keep behavioral assertions as the primary release gate because a screenshot cannot prove that a transition never started or that a control still works. Limit snapshots to states whose appearance carries accessibility meaning.

What should remain usable when animation is reduced?

All essential content, controls, focus behavior, status communication, and navigation should remain available. Reduced motion must not hide instructions, collapse diagrams, block clicks, remove meaningful progress, or cause overflow. The preferred result is a readable static or gently changing alternative that preserves the task without unnecessary movement.