In this video· click a step to jump to it
Test recording
Whatsite — chat wrapped walkthrough
52,382 ms · 0 pixels changed · matches baseline
We built this regression test for whatsite.vercel.app — it's yours to keep, free.
Take this test with you
This is a real Playwright test, recorded against whatsite.vercel.app. Claim it into your own workspace and re-run it on every deploy — free.
export async function test(page, baseUrl, screenshotPath, stepLogger) {
const shot = (n, slug) => screenshotPath.replace('.png', `-${n}-${slug}.png`);
const SCROLL_THROUGH = "(async () => { const ease = t => 1 - Math.pow(1 - t, 3); const max = Math.max(0, document.body.scrollHeight - window.innerHeight); if (max < 40) return; const anim = (a, b, ms) => new Promise(res => { const t0 = performance.now(); const step = now => { const p = Math.min(1, (now - t0) / ms); window.scrollTo(0, a + (b - a) * ease(p)); p < 1 ? requestAnimationFrame(step) : res(); }; requestAnimationFrame(step); }); await anim(0, max, 1400); await new Promise(r => setTimeout(r, 350)); await anim(max, 0, 800); })()";
const SCROLL_TO = (n) => "(async () => { const ease = t => 1 - Math.pow(1 - t, 3); const target = window.innerHeight * " + n + "; const from = window.scrollY; const anim = (a, b, ms) => new Promise(res => { const t0 = performance.now(); const step = now => { const p = Math.min(1, (now - t0) / ms); window.scrollTo(0, a + (b - a) * ease(p)); p < 1 ? requestAnimationFrame(step) : res(); }; requestAnimationFrame(step); }); await anim(from, target, 1100); await new Promise(r => setTimeout(r, 500)); })()";
const MAKE_CHAT = "(() => { const names = ['Alex', 'Sam']; const msgs = ['Morning! coffee?', 'always yes', 'ok 10:30 at the usual place', 'perfect', 'did you see the match last night?', 'unreal ending', 'haha I screamed', 'my neighbours hate me now', 'lunch tomorrow?', 'yes please', 'thinking ramen', 'you read my mind', 'ok booking it', 'love that', 'miss you', 'miss you more', 'send pics of the cat', 'she is asleep on my keyboard', 'iconic', 'see you saturday']; const lines = []; for (let d = 1; d <= 90; d++) { const day = String((d % 28) + 1).padStart(2, '0'); const mon = String((d % 9) + 1).padStart(2, '0'); for (let m = 0; m < 6; m++) { const who = names[(d + m) % 2]; const txt = msgs[(d * 3 + m) % msgs.length]; const hh = String(8 + ((d + m) % 12)).padStart(2, '0'); const mm = String((d * 7 + m * 11) % 60).padStart(2, '0'); lines.push(day + '/' + mon + '/2026, ' + hh + ':' + mm + ' - ' + who + ': ' + txt); } } const content = lines.join('\\n'); const f = new File([content], 'WhatsApp Chat with Sam.txt', { type: 'text/plain' }); const dt = new DataTransfer(); dt.items.add(f); const input = document.querySelector('input[type=file]'); input.files = dt.files; input.dispatchEvent(new Event('change', { bubbles: true })); return content.length; })()";
await page.context().setExtraHTTPHeaders({ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36' });
await page.route('**/cdn-cgi/scripts/**', function (r) { r.abort(); }).catch(function () {});
// ─── Scenario 1: signed-in landing with the drop zone ─────────────────────
stepLogger.log('Scenario 1: Signed-in landing');
await page.goto(baseUrl + '/', { waitUntil: 'domcontentloaded' });Checks run
Notes from the demo run
AI-generatedWe signed in to Whatsite with Google, uploaded a 90-day WhatsApp export (540 messages between two people) and walked the generated Chat Wrapped end to end. The analysis ran in the browser in a couple of seconds and produced Who writes more (270/270, a perfectly balanced conversation), Conversation Balance (17 vs 15 chars per message), Longest Streak (5 of 90 days), Record Day (Feb 2, 2026 - 6 messages), then Activity Heatmap, Longest Session and Peak Timing. The interpretation lines under each stat are what make it feel like Wrapped rather than a dashboard: "You're both fairly matched in detail" lands better than the raw number above it.
- The privacy claim is real, not marketingThe chat is parsed client-side - nothing is posted to a server. We watched the network while analysing and no upload fired. For a product asking people for their most private messages, that is the whole business, and you say it on the drop zone itself.
- Every stat card is independently downloadableEach card carries its own download control, so the share unit is one stat rather than the whole page. That matches how people actually post Wrapped-style results.
- Zero to result in one screenDrop the .txt, one Analyze Chat button, done - no account setup step between landing and the payoff, even though sign-in exists.
- Four locked cards stack back to back at the end of the scrollAfter Record Day, the remaining cards - Activity Heatmap, Longest Session, Peak Timing and the emoji ranking - are all Locked Stat with the identical "Unlock All Stats — $3" button one after another. The second half of the reveal reads as a paywall wall rather than a Wrapped. Interleaving one locked card between free ones, or blurring the real chart behind the lock so people see what they are buying, keeps the scroll rewarding right up to the ask.
- The wrapped page reserves ~8,700px of scroll, most of it emptyBelow the last card there is a long stretch of empty page before the footer CTA. On desktop the scrollbar implies much more content than exists; on mobile it is a lot of thumb travel through black. Trimming the reserved reveal height to the actual card count fixes it.
- Unlock All Stats / Get Pro·Demo runs never reach a paid checkout
Share this run
Proof your app is not AI slop
This test was built for whatsite.vercel.app — claim it free
One click copies the test and baseline screenshots into your own Lastest workspace. Re-run on every deploy and catch regressions before your users do — free, no card required.