In this video· click a step to jump to it
Test recording
DuoShot - app walkthrough
52,601 ms · 258,774 pixels changed
We built this regression test for duoshot.site — it's yours to keep, free.
Take this test with you
This is a real Playwright test, recorded against duoshot.site. Claim it into your own workspace and re-run it on every deploy — free.
export async function test(page, baseUrl, screenshotPath, stepLogger) {
const SITE = baseUrl.replace(/\/+$/, '');
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, 300)); await anim(max, 0, 700); })()";
// NOTE: page.evaluate(<string fn>, arg) silently returns undefined on the EB
// runner, so the dimensions are baked into each self-invoking IIFE string.
const MK_BODY = " const c = document.createElement('canvas'); c.width = w; c.height = h; const x = c.getContext('2d'); const g = x.createLinearGradient(0, 0, w, h); g.addColorStop(0, '#0b1220'); g.addColorStop(1, '#2563eb'); x.fillStyle = g; x.fillRect(0, 0, w, h); x.fillStyle = 'rgba(255,255,255,0.10)'; for (let i = 0; i < 6; i++) { x.fillRect(w * 0.08, h * (0.30 + i * 0.075), w * 0.84, h * 0.045); } x.fillStyle = '#ffffff'; x.textAlign = 'center'; x.font = 'bold ' + Math.floor(w / 9) + 'px sans-serif'; x.fillText(label, w / 2, h * 0.19); x.font = Math.floor(w / 26) + 'px sans-serif'; x.fillText(w + ' x ' + h, w / 2, h * 0.245); return c.toDataURL('image/png'); })()";
const MK_CLOSED = "(() => { const w = 1398, h = 2034, label = 'Closed';" + MK_BODY;
const MK_OPEN = "(() => { const w = 2007, h = 2853, label = 'Open';" + MK_BODY;
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' });
for (const pattern of ['**/cdn-cgi/scripts/**/email-decode.min.js', '**/sentry-cdn.com/**', '**/browser.sentry-cdn.com/**', '**/cdn.segment.com/**', '**/connect.facebook.net/**', '**/api-js.mixpanel.com/**']) {Checks run
Notes from the demo run
AI-generatedDuoShot is refreshingly narrow: one job (prepare iPhone Duo screenshot pairs for App Store review) done properly. We signed in and drove the whole workspace end to end. The strongest thing you built is the Check report: it reads the pair we imported and warns 'Fold text, open view: possible text beneath the fold' before Apple ever sees it. The exact pixel specs (1398x2034 closed, 2007x2853 open) printed right on the dropzones mean we never had to guess, and guest mode let us try the tool before making an account.
- The Check report is the productImporting a pair immediately produced a legibility warning about text under the fold. That is the moment the tool earns its price, and it happens in under five seconds without a single settings screen.
- Specs printed on the dropzonesClosed 5.4in 1398x2034 and Open 7.6in 2007x2853 are written on the import targets. We pasted correctly-sized images first try, which almost never happens on screenshot tools.
- Guest mode before signup/en/tool works unauthenticated with a GUEST badge, so an evaluator sees the canvas before committing an email. The authenticated state only adds sync and the ZIP export.
- The app-name field appends instead of replacingThe set name is pre-filled with the literal 'App'. Typing 'Harbor Notes' into it produced 'AppHarbor Notes' rather than replacing the placeholder. Making 'App' a real placeholder attribute instead of a value fixes it in one line.
- Login after signup says only 'Email not confirmed'Signup shows 'Check your email if confirmation is required', then login rejects with a bare 'Email not confirmed' and no resend link. Stating the confirmation requirement definitively on signup, plus a resend button on that error, would remove the dead end.
- /en/pricing checkout·Payments are not open yet, so there is nothing to exercise past the plan cards.
4 visual changes
Share this run
Proof your app is not AI slop
This test was built for duoshot.site — 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.











