Animation regression testing v0.1.0-alpha
Your code changed.See how yourinterface moves.
MotionPR drives your app in Chromium, records every animation and scroll as data, compares it with an approved baseline, and opens a report where both versions play on one clock.
npm install -D @erenthedeveloper0/motionpr@alphaBaseline 320 ms · cubic-bezier(0.16, 1, 0.3, 1)Candidate 560 ms · ease-in-out
The demo app's navigation panel as MotionPR captured it, 5× slower: one light per version rides the horizon with its real timing, and the arc opens as far as they differ.
- Native CSS
- WAAPI
- GSAP
- Anime.js
- Lenis
01 The change review cannot see
One line in a pull request.
A different interface in your hands.
.nav-panel {
transform: translateX(100%);
- transition: transform 320ms var(--expo-out);
+ transition: transform 560ms ease-in-out;
}
The diff reads like a tidy-up. MotionPR replays both versions on one clock and measures what changed:
The navigation panel of the demo app: baseline 320 ms on cubic-bezier(0.16, 1, 0.3, 1), candidate 560 ms on ease-in-out. MotionPR reported a duration and an easing change (warnings).
Real output · demo app · scenario navigation · checkpoint open
02 How it works
Capture. Analyze. Compare.
Inspect. Report.
Step 1 · Capture
Real input, in a real browser.
MotionPR opens each scenario in Chromium, injects its agent before your scripts run, and drives the page with clicks, hovers, key presses and wheel events. Every CSS transition, CSS animation and Web Animation is discovered as it starts; GSAP, Anime.js and Lenis report through adapters.
Step 2 · Analyze
Replayed at exact times, not recorded.
When motion settles, the window is replayed deterministically: native animations through paused clones, engines with callbacks muted. Computed styles, geometry and frames are sampled at chosen times, so the same build gives the same data on every run.
Step 3 · Compare
Matched by identity. Compared as curves.
Records are paired across versions by element identity, type and name. Durations, delays, easing curves (65 samples each), staggers, trajectories, timelines, scroll metrics and settled pixels are compared, and every difference is graded by a named rule.
Step 4 · Inspect
Both versions, one clock.
The offline report plays baseline (blue) and candidate (peach) side by side, with each animation's delay, duration and easing on a shared timeline, and the largest deviation marked on the curve.
Step 5 · Report
An answer your CI understands.
A Markdown summary in the pull request's run, the report as an artifact, and an exit code:
0within thresholds,1for a regression,2for an error. Uncertainty is reported as uncertain, never as a failure.
03 Measured, not guessed
55 differences.
Every one intended. Nothing else.
The demo app ships in two variants with eleven deliberate motion changes across every engine. This is MotionPR's real comparison of the two: filter it the way you would a pull request. The repository's end-to-end suite checks that every change is found and nothing else is reported.
1 critical, 35 warnings, 18 info, 1 uncertain.
04 Engines
The browser's animations, natively.
Your engine's, through one line.
No setup · recorded automatically · verified in Chromium
.nav-panel {
transform: translateX(100%);
transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.nav-open .nav-panel {
transform: translateX(0);
}- warningDuration 320ms → 560ms (+240ms, +75%)
- warningEasing cubic-bezier(0.16, 1, 0.3, 1) → ease-in-out (max deviation 70% of progress at 27% time)
- infotranslateX path deviates up to 278.99px at 26% progress
No setup · element.animate() is recorded automatically
cards.forEach((card, i) =>
card.animate(
[{ opacity: 0, transform: "translateY(28px)" }, { opacity: 1, transform: "none" }],
{ id: "work-reveal", duration: 520, delay: i * 70, fill: "both" },
),
);- warningStagger interval 70ms → 120ms across 6 elements
- warningDelay 140ms → 240ms (+100ms, +71.43%)
- warningDelay 210ms → 360ms (+150ms, +71.43%)
One line · verified with GSAP 3.15.1 (range >=3.12 <4)
import gsap from "gsap";
import { motionprGsap } from "@erenthedeveloper0/motionpr/gsap";
motionprGsap.register(gsap); // a no-op unless MotionPR is capturing- warningTimeline duration 1310ms → 1860ms
- warningLabel “underline” moved 510ms → 810ms
- warningLabel “actions” moved 660ms → 960ms
Wrap at creation · verified with Anime.js 4.5.0 (v4)
import { animate, createTimeline, eases } from "animejs";
import { createAnimeInstrumentation } from "@erenthedeveloper0/motionpr/animejs";
const motion = createAnimeInstrumentation({ eases });
export const track = motion.wrapAnimate(animate);
export const timeline = motion.wrapTimeline(createTimeline);- warningStagger interval 60ms → 110ms across 4 elements
- warningDelay 120ms → 220ms (+100ms, +83.33%)
- warningDelay 180ms → 330ms (+150ms, +83.33%)
One line · scroll motion, recorded live · verified with Lenis 1.3.27
import Lenis from "lenis";
import { motionprLenis } from "@erenthedeveloper0/motionpr/lenis";
const lenis = new Lenis({ autoRaf: true });
motionprLenis.attach(lenis);- warningScroll takes 1228ms → 2045ms to settle (+817ms)
- warningScroll-linked progress at the same scroll position differs by up to 14% (at 4303px: 14% → 0%)
- infoScroll setting lerp: 0.1 → 0.06
05 Easing analysis
Curves, not strings.
ease and cubic-bezier(0.25, 0.1, 0.25, 1) are the same curve; ease-out and a tuned bezier can feel nothing alike. MotionPR compares 65 samples of each curve. This lab runs MotionPR's own easing code in your browser.
06 Scroll motion
A scroll is not an animation.
It is measured like one should be.
Smooth scrolling maps time to position. MotionPR records it live, never replays it, and compares distance, settle time, the shape of the movement, peak velocity and overshoot. Below: the demo's Lenis scroll at lerp 0.1 and 0.06, as recorded.
07 Continuous integration
Least privilege.
Most signal.
npx motionpr init --github writes a workflow that needs only contents: read, pins every action to a commit SHA, and uploads the report even when the job fails.
- 0
- Within thresholds
- 1
- A difference reached
comparison.failOn - 2
- Configuration or execution error
◐ motionpr 0.1.0-alpha · run
→ 1 scenario from motionpr.config.mjs
→ Web server ready at http://localhost:4173 (263 ms)
✔ menu-open@default 1 checkpoint · 1 animation · 2.0s
Baseline 20261113-215354-f7d6 → Current 20261113-215403-2778
✖ menu-open@default 1 critical · 1 warning · 1 info
✖ Duration 300ms → 600ms (+300ms, +100%)
▲ Easing cubic-bezier(0.16, 1, 0.3, 1) → ease-in-out
● translateX path deviates up to 195.3px at 26% progress
Result FAILED fail on: critical
exit code 1
Review motion like code.
Open source, MIT licensed, runs on your machine. No account, no backend, no telemetry.
npm install -D @erenthedeveloper0/motionpr@alpha