Excessibility.SnapshotDiff (Excessibility v0.14.0)

View Source

Cross-snapshot diffing: compare two HTML snapshots of the same view and report the regions whose rendered content changed.

This is a capability no single-snapshot tool (axe-core included) can provide: by comparing a before and after of the same template it can detect that visible content changed. The first consumer is the accessibility check in live_region_findings/3 — LiveView patches the DOM without a page load, so content that updates outside an aria-live region (or role="alert"|"status"|"log", or <output>) is never announced to screen-reader users (WCAG 2.1 SC 4.1.3, Status Messages).

diff/3 itself is content-agnostic and returns the structural delta, so it doubles as a general-purpose semantic DOM diff for other callers.

Diff strategy

Both snapshots are parsed and walked in parallel from <body> down, matching element children positionally by tag. The change is localized to the deepest element whose subtree text differs but whose children still line up — i.e. the smallest stable container that owns the change. When the child structure itself diverges (rows added/removed, tags differ), that parent is reported as the changed region. Text is whitespace-normalized, so reflow/indentation differences are ignored.

Summary

Types

A single changed region produced by diff/3.

Functions

Diff two HTML snapshots and return the list of changed regions.

Return accessibility findings for content that changed outside any live region, in the Excessibility.LiveViewRules.Rule.finding/0 shape.

Diff snapshot files on disk, grouped by the test that produced them.

Run live_region_findings/3 over each consecutive pair in a list of snapshots captured for the same test, and concatenate the findings.

Types

region()

@type region() :: %{
  change: :changed,
  selector: String.t(),
  tag: String.t(),
  old_text: String.t(),
  new_text: String.t(),
  announced: boolean(),
  element: String.t()
}

A single changed region produced by diff/3.

Functions

diff(old_html, new_html, opts \\ [])

@spec diff(String.t(), String.t(), keyword()) :: [region()]

Diff two HTML snapshots and return the list of changed regions.

Each region is the deepest stable container whose normalized text content differs between old_html and new_html. Returns [] when the documents are content-equivalent (whitespace aside) or either fails to parse.

live_region_findings(old_html, new_html, opts \\ [])

@spec live_region_findings(String.t(), String.t(), keyword()) :: [
  Excessibility.LiveViewRules.Rule.finding()
]

Return accessibility findings for content that changed outside any live region, in the Excessibility.LiveViewRules.Rule.finding/0 shape.

A change is considered announced (and therefore not flagged) when the changed region or any of its ancestors is an aria-live region (a value other than "off"), carries role="alert"|"status"|"log", or is an <output> element.

scan_files(paths, opts \\ [])

@spec scan_files(
  [Path.t()],
  keyword()
) :: [{Path.t(), Excessibility.LiveViewRules.Rule.finding()}]

Diff snapshot files on disk, grouped by the test that produced them.

Auto-captured snapshots embed a Test:/Sequence: metadata comment; this groups files by test, orders them by sequence, and diffs each consecutive pair. Returns {file, finding} tuples where file is the later snapshot of the pair the finding came from.

Snapshots without that metadata (the default Module_line.html naming carries no reliable test boundary) are skipped, so this is inert unless capture metadata is present.

scan_sequence(snapshots, opts \\ [])

@spec scan_sequence(
  [String.t()],
  keyword()
) :: [Excessibility.LiveViewRules.Rule.finding()]

Run live_region_findings/3 over each consecutive pair in a list of snapshots captured for the same test, and concatenate the findings.