Animation identification
How MotionPR recognises the same element and the same animation in two versions of your app.
A comparison is only as good as its pairing. Before MotionPR can say "this transition got slower", it has to decide that two records, one per capture, are the same animation.
Element identity
Every animated element gets an identity key, chosen in this order:
| Priority | Method | Key example | Confidence |
|---|---|---|---|
| 1 | motionpr-id | motionpr-id:menu | high |
| 2 | test-id | test-id:nav-panel | high |
| 3 | dom-id | id:pricing (unique, not generated) | medium |
| 4 | aria | aria:button[Open menu] | medium |
| 5 | dom-path | path:test-id:work>div:nth-of-type(2) | low |
identity.attribute(defaultdata-motionpr-id) andidentity.testIdAttribute(defaultdata-testid) are configurable.- An
idcounts only when it is unique in the page and does not look generated: React's:r1:,«r1», prefixes such asradix-,headlessui-,mui-,ember,ng-, long hex runs and digit-heavy ids are skipped. - An accessible label counts for
aria-label, and for the text of leaf buttons, links, tabs and menu items when it is unique among elements of the same kind. - A DOM path is anchored at the nearest ancestor with a stronger identity, so
path:test-id:work>div:nth-of-type(2)survives changes elsewhere in the page.
The record id combines the element key, the animation type and its name:
motionpr-id:menu::css-transition::transform. Duplicates get #2, #3.
Matching
Records are matched per checkpoint in three deterministic passes:
| Pass | Rule | Confidence |
|---|---|---|
| exact | Identical record id | the lower of the two sides |
| element | Same element and type, different name, and exactly one such record on each side (a renamed @keyframes) | medium, or low |
| fuzzy | Same type, name and tag, the same accessible label or selector, and at least 50% property overlap | always low |
Anything left over is added (only in the candidate) or removed (only in the baseline). An element pass records a note such as Same element, renamed from "process@discover" to "process@design"; the demo app's Anime.js timeline produces exactly that.
Every difference in the demo app's real comparison (changed, added and removed records), filterable by severity, engine and text:
Interactive diff viewer: needs JavaScript.
Low confidence means uncertain
With comparison.uncertainOnLowConfidence (default true), every change on a pair where either
side was identified by DOM position only is reported as uncertain, and an uncertain
difference never fails a run unless failOnUncertain is set. A wrong pairing therefore cannot
produce a false regression; it produces a question. The capture also carries a
LOW_CONFIDENCE_IDENTITY note with the number of such animations.
The demo app has one on purpose: a decorative glow without an id inside the work grid. Its
duration change is reported as uncertain with the detail (element matched by DOM position
only).
Give important elements an id
<nav data-motionpr-id="menu">…</nav>
<dialog data-motionpr-id="booking-dialog">…</dialog>
<li data-motionpr-id="card-1">…</li>Numbered ids such as card-1, card-2 are also what stagger detection groups by: siblings whose
keys differ only by a trailing number form one stagger family. See
Motion comparison.
GSAP and Anime.js records are additionally named by the adapter (hero-intro@lines), so name
timelines you care about with track(timeline, "name").