Scrnify blog
How to Take Screenshots of Lovable, Bolt, and Replit Apps Automatically
Hey there! Laura and Heidi here from SCRNIFY!
Lovable, Bolt, and Replit are great at turning an idea into a working web app quickly.
That speed creates a small problem: the app can change faster than your memory of it. Ask us how we know.
A prompt tweaks the layout. A generated component shifts the mobile view. A preview link gets shared before anyone checks the empty state. The app still loads, but the UI is not the same UI you thought you shipped.
Automatic screenshots help here. They give you a visual record of what the app looked like at a specific URL, viewport, and point in time. Not glamorous. Extremely useful when the demo is tomorrow.
This guide covers how to capture Lovable, Bolt, and Replit apps automatically, where screenshots fit in your QA loop, and when you should use local browser automation instead of a remote Capture.
Short answer
If your app has a public or shareable URL, you can usually automate screenshots with one of three approaches:
| Approach | Best for |
|---|---|
| Manual scheduled Capture | Quick recurring screenshots of public app URLs |
| CLI or API Capture | CI jobs, scripts, docs updates, and deploy preview checks |
| Playwright screenshot | Authenticated flows, interaction-heavy states, and DOM assertions |
Use remote Capture when the page state is reachable by URL.
Use Playwright when the screenshot depends on logging in, clicking through a flow, seeding local state, or checking DOM conditions before capture.
For many Lovable, Bolt, and Replit apps, start with remote screenshots of the public preview URL. Add deeper browser tests only when the app has state that a URL alone cannot recreate. Start small. Your future test suite will thank you.
Why screenshots matter for AI-built apps
AI-built apps tend to have visible failure modes before they have obvious test failures.
Common examples:
- Desktop layout looks fine, mobile layout hides the main CTA
- Generated cards assume short labels and break with real copy
- Empty state is missing because sample data was always present
- Modal cannot scroll on small screens
- Dark mode changes one text color but not its background
- Header, footer, or chat widget covers a form button
- App preview loads a blank shell while data is still fetching
Logs will not show most of that. A screenshot will.
The screenshot is not the whole QA process. It is evidence you can attach to a PR, share in Slack, compare after a prompt change, or keep as a record before a demo.
Think of it as the least dramatic way to answer, "wait, did the page look like that before?" We have asked that question too many times without a screenshot nearby.
Step 1: decide what URL to capture
Start with the URL someone else would open.
For AI app builders, that might be:
- Lovable published app URL
- Bolt deploy preview URL
- Replit app URL
- Netlify, Vercel, or Cloudflare preview created from the generated app
- Production URL after you connect a custom domain
Use the most stable URL for the job.
For a quick visual check, the current preview URL is fine. For scheduled monitoring, use a production URL or a stable branch preview. For release review, capture the exact deploy preview tied to the PR or commit.
Save that URL with the screenshot. Six weeks later, the image alone will not tell anyone what environment it came from. Future you deserves better than detective work and Slack archaeology.
Platform notes: Lovable, Bolt, and Replit
The idea is the same across all three tools: capture the URL a reviewer can open. The annoying details are different.
| Platform | What to capture | Watch out for |
|---|---|---|
| Lovable | Published app URL or connected custom domain | Some work-in-progress previews are not meant for scheduled checks. Use the published URL when you want repeatable captures. |
| Bolt | Deploy preview or exported app deployed to your host | The Bolt editor preview is not always the right target. Capture the deployed app when possible. |
| Replit | Public .replit.app URL or custom domain |
Apps can take a moment to wake up. Use a readiness check or retry before treating a blank page as a real failure. |
We have seen this exact pattern with generated apps: the first prompt creates a good desktop page, the next prompt adds a section, and suddenly the mobile CTA is somewhere under the footer. Nobody touched the footer on purpose. The screenshot still catches it.
If the app is private, behind auth, or only visible inside an editor preview, remote URL capture may not be enough. In that case, use Playwright to open the app in a real browser session, sign in if needed, and capture the state after the setup steps.
Step 2: capture desktop and mobile
Do not capture only desktop.
Generated apps often look strongest at the viewport used during development. The first real problem shows up when the same page is opened on a phone-sized screen.
A practical first pass:
1440x900 desktop review
390x844 mobile review
For each important page, save both:
home-desktop.png
home-mobile.png
pricing-desktop.png
pricing-mobile.png
signup-desktop.png
signup-mobile.png
Keep filenames boring. We love boring here. Boring filenames are easier to sort, attach, and compare.
Step 3: wait for the app to be ready
A screenshot taken too early is worse than no screenshot because it creates false noise.
Modern generated apps often fetch data after the shell loads. If you capture immediately after navigation, you may record a spinner, blank state, or half-rendered layout that users never see.
That is how screenshot folders turn into little anxiety museums.
Good readiness signals:
- Main heading is visible
- App shell is visible
- Expected card, form, table, or CTA is visible
- Loading skeleton is gone
- Error banner is not present
If you control the app, add stable selectors for important states:
<main data-testid="app-ready">
...
</main>
Then a browser test can wait for that before taking a screenshot:
await page.goto('https://example.replit.app')
await page.getByTestId('app-ready').waitFor({state: 'visible'})
await page.screenshot({path: 'home-desktop.png', fullPage: true})
For remote URL capture, choose pages that become ready from URL alone. If the page needs many clicks first, use Playwright or another browser automation tool for that state.
Step 4: capture the states that usually break
The homepage is useful. It is not enough.
For Lovable, Bolt, and Replit apps, check the states that prompts often under-specify:
| State | Why it matters |
|---|---|
| Empty state | Generated UI often assumes data exists |
| Long content | Real labels break neat demo cards |
| Mobile | Desktop-first generated layouts hide actions |
| Error state | Validation copy may overflow or appear in odd places |
| Loading state | Skeletons can shift the page after capture |
| Modal open | Scroll and fixed positioning bugs show up fast |
| Dark mode | One missed token can make text unreadable |
You do not need to automate every state on day one. Please do not build a giant screenshot fortress before the app has users. Pick states where a visible bug would block a demo, confuse a user, or make a screenshot embarrassing to share.
Option 1: use a CLI for URL-based screenshots
If your app URL is reachable from the internet, a screenshot CLI is the fastest path from "here is the preview" to "here is proof of what it looked like."
With scrnify, install the Homebrew Formula:
brew install scrnify/tap/scrnify
Set your API key, then capture the app:
export SCRNIFY_API_KEY=your_api_key_here
scrnify capture https://example.replit.app \
--type image \
--format png \
--full-page
For a Lovable or Bolt app, swap in the published, preview, or custom-domain URL:
scrnify capture https://your-app.example.com \
--type image \
--format png \
--full-page
Under the hood, scrnify sends the Capture to the Scrnify API. Your script does not need to install Chrome, manage Playwright browsers, or clean up browser processes.
That is the whole point. Browser babysitting is not the fun part of building a tiny AI app. We say this with feeling.
It is less useful when the screenshot depends on a private browser session. For that, use local browser automation.
Option 2: use Playwright when the app needs interaction
Some screenshots cannot be captured from a URL alone. This is where the "just screenshot the URL" plan starts wobbling.
Examples:
- User must log in
- App needs test data created first
- Modal must be opened before capture
- Form validation must be triggered
- Toggle or tab changes the visible state
- Page depends on local storage or cookies
Use Playwright for those states:
import {test, expect} from '@playwright/test'
test('captures generated app signup flow', async ({page}) => {
await page.setViewportSize({width: 390, height: 844})
await page.goto('https://preview.example.com')
await page.getByRole('link', {name: 'Sign up'}).click()
await expect(page.getByRole('heading', {name: 'Create account'})).toBeVisible()
await page.screenshot({path: 'signup-mobile.png', fullPage: true})
})
The important part is not Playwright itself. The important part is the wait before the screenshot. Make the test prove the relevant UI is visible before saving the image. Otherwise you have automated a bad guess.
Option 3: run screenshots from CI
Automatic screenshots become more useful when they run after changes.
For a generated app connected to GitHub, add a CI step that captures the preview or production URL after deploy.
A simple workflow shape:
1. Build or deploy app
2. Get preview URL
3. Capture desktop screenshot
4. Capture mobile screenshot
5. Upload screenshots as artifacts
6. Link screenshots in PR or release notes
If the app is public, the capture step can use a CLI command:
scrnify capture "$PREVIEW_URL" \
--type image \
--format png \
--full-page \
--cache-ttl 0
For desktop and mobile, keep the script painfully obvious. The API version makes the viewport explicit:
set -euo pipefail
: "${PREVIEW_URL:?Set PREVIEW_URL first}"
: "${SCRNIFY_API_KEY:?Set SCRNIFY_API_KEY first}"
mkdir -p screenshots
curl -G 'https://api.scrnify.com/capture' \
--data-urlencode "key=$SCRNIFY_API_KEY" \
--data-urlencode "url=$PREVIEW_URL" \
--data-urlencode 'type=image' \
--data-urlencode 'format=png' \
--data-urlencode 'fullPage=true' \
--data-urlencode 'width=1440' \
--data-urlencode 'height=900' \
--output screenshots/home-desktop.png
curl -G 'https://api.scrnify.com/capture' \
--data-urlencode "key=$SCRNIFY_API_KEY" \
--data-urlencode "url=$PREVIEW_URL" \
--data-urlencode 'type=image' \
--data-urlencode 'format=png' \
--data-urlencode 'fullPage=true' \
--data-urlencode 'width=390' \
--data-urlencode 'height=844' \
--output screenshots/home-mobile.png
That is not fancy. Good. Fancy CI screenshot scripts have a way of becoming their own side project.
A GitHub Actions job might look like this:
name: Capture app screenshots
on:
workflow_dispatch:
inputs:
preview_url:
description: 'Lovable, Bolt, Replit, or deploy preview URL'
required: true
jobs:
screenshots:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Capture desktop and mobile
env:
SCRNIFY_API_KEY: ${{ secrets.SCRNIFY_API_KEY }}
PREVIEW_URL: ${{ inputs.preview_url }}
run: |
mkdir -p screenshots
curl -G 'https://api.scrnify.com/capture' \
--data-urlencode "key=$SCRNIFY_API_KEY" \
--data-urlencode "url=$PREVIEW_URL" \
--data-urlencode 'type=image' \
--data-urlencode 'format=png' \
--data-urlencode 'fullPage=true' \
--data-urlencode 'width=1440' \
--data-urlencode 'height=900' \
--output screenshots/home-desktop.png
curl -G 'https://api.scrnify.com/capture' \
--data-urlencode "key=$SCRNIFY_API_KEY" \
--data-urlencode "url=$PREVIEW_URL" \
--data-urlencode 'type=image' \
--data-urlencode 'format=png' \
--data-urlencode 'fullPage=true' \
--data-urlencode 'width=390' \
--data-urlencode 'height=844' \
--output screenshots/home-mobile.png
- uses: actions/upload-artifact@v4
with:
name: app-screenshots
path: screenshots
The workflow shape is what matters: URL in, desktop and mobile screenshots out, artifacts attached.
For private flows, run Playwright in CI and upload the screenshots it creates.
Either way, keep the output attached to the change. Screenshots hidden on a developer laptop do not help review. They mostly help someone say, "I swear it looked fine locally."
What to save with each screenshot
A screenshot needs context.
At minimum, save:
- URL
- Viewport
- Capture time
- Commit or deploy ID
- Environment
- Step name
- Tool used for capture
That can be a small manifest next to the images:
{
"url": "https://example.replit.app",
"viewport": "390x844",
"capturedAt": "2026-06-08T12:00:00.000Z",
"commit": "abc1234",
"environment": "production",
"step": "home-mobile",
"tool": "scrnify"
}
This keeps old screenshots from turning into mystery images. We have enough mysteries in browser automation already.
Name screenshots like review artifacts
Good screenshot names describe the page, viewport, and state:
01-home-desktop.png
02-home-mobile.png
03-dashboard-empty-mobile.png
04-settings-modal-desktop.png
05-signup-error-mobile.png
Avoid names like:
screenshot.png
final.png
test2.png
new-new.png
Those names make review harder than it needs to be. Nobody wants archaeology during a release check.
Compare screenshots carefully
After you have repeatable screenshots, you may want visual diffs.
That can work well for stable pages:
- Home page
- Pricing page
- Docs page
- Component demo page
- Empty state
- Public app shell
It gets noisy on pages with:
- Live timestamps
- Random sample data
- User-generated content
- Ads or third-party widgets
- Animations captured mid-transition
- AI-generated copy that changes often during drafting
Do not turn every screenshot into a strict pixel test. Start by saving screenshots as review artifacts. Add visual diffs only after the page is stable enough that a diff means something.
A practical screenshot loop for Lovable, Bolt, and Replit apps
For a small AI-built app, start here:
- Pick the public preview or production URL
- Capture home page at
1440x900and390x844 - Capture the most important product route at both sizes
- Capture one state likely to break: empty, error, modal, or long content
- Save URL, viewport, commit, and timestamp with the images
- Attach screenshots to PRs, release notes, demos, or QA reports
- Turn repeated visual bugs into focused browser checks
That loop is small enough to keep. It still catches many visible problems before users do.
When automatic screenshots are the wrong tool
Screenshots are for visible state. They do not prove everything.
Do not rely on screenshots alone for:
- API correctness
- Database writes
- Accessibility semantics
- Auth and permission rules
- Analytics events
- Security-sensitive flows
Use tests, logs, network inspection, and accessibility checks for those. Keep screenshots as visual evidence beside them.
Also avoid capturing real user data. AI app builders make it easy to ship quickly, but screenshots can still leak names, emails, billing details, messages, or tokens. Use test accounts and seed data for shared artifacts.
Checklist
Use this before automating screenshots for a Lovable, Bolt, or Replit app:
- Is the target URL stable enough to capture repeatedly?
- Does the page become ready without manual interaction?
- Do you need desktop and mobile screenshots?
- Which state is most likely to break visually?
- Are filenames easy to understand later?
- Are URL, viewport, timestamp, and commit saved with the image?
- Should this be remote Capture or a Playwright screenshot?
- Will the screenshot be attached somewhere reviewers can see it?
Automatic screenshots will not make an AI-built app correct by themselves. Sorry. We would love that too.
They will make changes visible. That is the useful part.
Try the SCRNIFY open beta and review current pricing. scrnify.com
If you are building screenshot workflows around Lovable, Bolt, Replit, or other AI app builders, we'd like to hear what keeps breaking. Drop us a line at support@scrnify.com or find us on Twitter @scrnify.
Cheers, Laura & Heidi