loupfeed › Demos
Screen recordings go stale the day after you make them, and automated ones look like a robot filling in a form. loupfeed plays a scenario file the way a person would use your app — the cursor travels, the page scrolls, the typing hesitates — and records the take. Re-run it every release instead of recording it again.
Playwright on the web, Maestro on mobile · npm i @loupfeed/demo-core
This is the part you cannot get from a normal test runner. Every interaction goes through a humanizer, and the rules are not optional — a scenario cannot opt out of looking human.
The cursor visibly moves to its target on an eased, bowed, slightly jittery curve — with a dwell before the press, and overshoot-and-settle on long reaches.
Nothing is clicked unless it is genuinely in the viewport. There are no invisible, teleported clicks anywhere in a take.
Off-screen targets are brought into view with wheel-burst scrolling — flick, coast, pause — never a programmatic jump.
Per-key cadence with hesitation after words and punctuation, instead of text appearing in a field all at once.
All randomness draws from one seeded stream, so the same scenario and the same seed produce byte-for-byte the same take.
A demo is JSON in your repo, not a binary you re-record. It goes through code review like anything else, and when the UI changes you edit a line instead of booking an afternoon.
fb: target the same opaque element ids the build plugins stamp for feedback — they survive refactors${VAR} placeholders resolve from the environment at play time, so credentials never enter the fileread steps hold a beat on something worth seeing; expectVisible asserts the transition actually happenedThat division of labour is the whole design. An agent is good at deciding what a demo should show and terrible at making playback look human — so it never drives the browser directly. It discovers your app, writes a scenario grounded in what it found, and the engine plays it back under the invariant.
demo_discover crawls the navigable surface link by link, collapses id-bearing URLs onto route templates, and emits an app map: every route with its headings, its interactive elements — each carrying a scenario-ready selector — and its outgoing links.
Version one follows links and records buttons without pressing them, so screens reachable only through a button land in unvisited rather than being silently missed.
On a phone there is no visible cursor, so looking human reduces to pacing, flick-style scroll physics and settle beats. @loupfeed/demo-native compiles the same scenario schema into a Maestro flow and records on-device, rather than making you maintain a second script.
fb:<id> targets the element id the React Native SDK exposes as the view's testIDscroll becomes a burst of decaying percentage swipes — flick, then coastread and pause become an animation wait plus a held beat| Scenario step | Maestro |
|---|---|
| goto | openLink via deep link, or launchApp |
| click | tapOn by id or text |
| type | tapOn + inputText |
| scroll | decaying swipe burst |
| scrollTo | scrollUntilVisible |
| expectVisible | extendedWaitUntil |
| recording | startRecording / stopRecording |
Status, plainly: the compiler and runner are unit-tested against Maestro's documented command set. The end-to-end proof on a real simulator is the next step, tracked in the open issue — so treat mobile playback as newer than the web player.
The launch video, the release note clip and the onboarding walkthrough all regenerate from the same file after a UI change.
Every expectVisible is an assertion. A demo that stops playing is telling you a real flow stopped working.
Seeded playback means a re-run is comparable to the last one — no “it looked fine when I recorded it”.
Free to self-host, forever. Playwright is a peer dependency — install it where the player runs and you are going.