Recording of the visual regression run on duoshot.site · DuoShot - app walkthrough.

In this video· click a step to jump to it

D
Passed ✓

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/**']) {
+98 more lines — claim the test to get the full code.

Checks run

Run
✓
Visual
13.56%
Text
—
DOM
✓
Network
+0 −11
Console
✓
A11y
A
Perf
1067 over
URL
✓
Variables
—
Full report
Log in →
258,774
Diff px
53s
Duration
A
Accessible
WCAG 2.2 · 100
C
Fast
Web Vitals · 70

Notes from the demo run

AI-generated

DuoShot 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.

Highlights
  • The Check report is the product
    Importing 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 dropzones
    Closed 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.
Friction points
  • The app-name field appends instead of replacing
    The 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.
Couldn't reach
  • /en/pricing checkout·Payments are not open yet, so there is nothing to exercise past the plan cards.

4 visual changes

Step 1198,277 px changed
Before
Before
After
After
Diff
Diff
Step 394 px changed
Before
Before
After
After
Diff
Diff
Step 725,459 px changed
Before
Before
After
After
Diff
Diff
Step 1034,944 px changed
Before
Before
After
After
Diff
Diff

Share this run

Earned · Lastest awards

Proof your app is not AI slop

LASTEST
starter
a11y
WCAG AA
regressions
0
Badges stay live, ratchet upward, only downgrade on confirmed regression.
57,290 test runs recorded · 1,169 products tested with Lastest

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.