| name | playwright-scraper |
| description | Build a Playwright walkthrough script that signs into a web app, walks every step, fills forms with sensible defaults, answers Yes/No wizards, takes a numbered screenshot at each step, and produces an HTML gallery + composite PNG. Use when the user asks to "walk through" a URL with Playwright, "screenshot every step" of an app, "scrape" a Lovable/v0/Bolt/Streamlit prototype, generate UI documentation, capture a flow for design review, or build a regression baseline of an app's UI. |
Playwright walkthrough builder
You are about to write a Playwright script that walks through a web app the user gave you and produces a labeled screenshot gallery.
This skill ships with a helper library at playwright_scraper (see the repo at https://github.com/ChakshuGautam/playwright-scraper). Prefer using it over re-implementing — the helpers were tuned against real prototypes (Lovable apps in particular) and handle the edge cases listed below.
Required output
Always produce three artifacts in an output directory:
- Numbered screenshots —
01_signin_blank.png, 02_signin_filled.png, ... one per step
index.html — a click-to-zoom gallery
_graph_all.png — a single composite PNG of every screenshot
The user wants to see the flow without re-running anything, so the gallery is the deliverable, not the script.
How to write the script
import asyncio
from playwright.async_api import async_playwright
from playwright_scraper import Walker
OUT = "output"
URL = "https://example.com/"
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
ctx = await browser.new_context(viewport={"width": 1440, "height": 900})
page = await ctx.new_page()
page.on("pageerror", lambda exc: print(f"[pageerror] {exc}"))
w = Walker(page, OUT)
await page.goto(URL, wait_until="networkidle", timeout=60000)
await page.wait_for_timeout(1500)
await w.shot("landing")
previous_text = ""
for i in range(20):
await w.smart_fill()
await w.pick_first_radio_in_each_group()
await w.check_consents()
await w.answer_yesno("No")
await w.shot(f"step_{i:02d}_filled")
clicked = await w.click_forward()
if not clicked:
await w.shot(f"step_{i:02d}_final")
break
await page.wait_for_timeout(2500)
await w.shot(f"step_{i:02d}_after_{clicked.lower().replace(' ', '_')}")
cur_text = (await w.page_text())[:500]
if cur_text == previous_text:
break
previous_text = cur_text
w.build_gallery(title="example.com walkthrough")
await browser.close()
asyncio.run(main())
Patterns the helpers already know
Walker.shot(label) — saves NN_<label>.png and a sibling .txt with the page's innerText (extremely useful for debugging: when something goes wrong, read the .txt rather than re-running).
Walker.smart_fill() — fills every empty visible <input> / <textarea> with a sensible default based on its placeholder / name / id / aria-label. Knows about: email, phone, name (first/last/full/business), address, city/state/country, PIN/GST/PAN/Aadhaar, dept, fee, SLA, description, code/slug, and more. Pass overrides={"workspace": "GOI"} to override values by hint-substring match.
Walker.answer_yesno(choice="No") — finds every Yes/No button pair on the page (grouped by parent element) and clicks the chosen side. Wizards with multiple questions per step need ALL answered before "Continue" enables.
Walker.click_forward() — tries every reasonable forward-progress label: Continue, Next, Submit, Save and continue, Confirm, Finish, Done, Publish, Launch, Create, Apply, Start Application, etc. Returns the label clicked or None.
Walker.build_gallery(title=...) — emits index.html and _graph_all.png from the saved screenshots.
Pitfalls to avoid (learned the hard way)
-
Don't smart-fill password fields. smart_fill already skips them. For sign-in, fill #password (or whatever selector) directly with the user-provided value. For "reset password" steps, ALSO fill explicitly — using a generic default will fail validation with "Current password is incorrect."
-
Wizards with many Y/N questions per step. A single step may have 3 separate questions; "Continue" stays disabled until all are answered. Call answer_yesno and check it returned > 0 — if so, look at the page again, more might have appeared. Repeat up to a few times.
-
State-machine wizards keep the same URL. Many SPA wizards mutate state without changing the URL. Don't rely on URL change to detect "we moved forward" — compare page text (page_text() first 300-500 chars) before vs after.
-
Look at the page after the first failed click_forward. When forward returns None, the screenshot of the current state will tell you why (a Yes/No you missed, an unchecked consent, an invalid email). Don't just retry — look.
-
Phone-frame previews are NOT mobile viewports. Lovable-style apps often embed a citizen view inside a phone-mockup at desktop viewport. Don't switch to a mobile device profile — the app already renders both frames inside one desktop page. Look for desktop-frame toggle buttons (icons named monitor/laptop) to switch.
-
Common forward labels you'll miss without click_forward: "Start Application", "Use template", "Go Live", "Publish", "Choose Template". The helper's default list includes these.
-
Tab clicks via :text-is('Foo') can accidentally match anything containing 'Foo'. Use get_by_role("tab", name="Foo") first; fall back to :text-is. Some UI library "tabs" are really <button> — try both.
-
The .txt sidecar from shot() is your friend. Reading the rendered body text after each step is faster than re-running with --headed. When the script hits a wall, grep -l 'Continue' output/*.txt shows which steps still had a Continue button.
-
Avoid mocking the auth. If sign-in is "anything@anything + temp password", just fill realistic values rather than trying to bypass.
-
Run with headless=True by default. The user almost always wants the gallery, not to watch the browser. Only flip headed if you're actively debugging.
Workflow when the user gives you a URL
- Reconnaissance pass first. Write a 20-line script that just loads the page and dumps controls (inputs + buttons + their text/placeholders). Read that output before writing the full walkthrough.
- Identify auth. Ask the user for credentials if they aren't obvious. For prototypes ("temp password 12345678" is common), try the password they gave with a placeholder email like
admin@organization.gov.
- Write the walkthrough. Hand-code the first 2-3 known steps (auth + first nav), then loop with
smart_fill + click_forward for the rest.
- Run, inspect the gallery, fix gaps. If the script gets stuck at step N, open
NN_*.txt, figure out what the page wants, adjust, re-run.
- Tour every sidebar/nav route too. Once authenticated, iterate over every link in the navigation and screenshot each page — this catches hidden features the linear walkthrough misses.
- Build the gallery and tell the user where to view it.
What the deliverable looks like
The user gets a folder they can cd into and open index.html. Each screenshot has a one-line caption derived from the filename. The composite PNG lets them see the whole flow on one screen.
If the user also has a static hosting setup (e.g. an nginx wildcard like *.proto.example.com), offer to drop the folder into /var/www/<slug>/, add an nginx block, and provision a Let's Encrypt cert. The gallery is just static HTML+PNG.