| name | signup-flow-driver |
| description | Use when an end-to-end signup, email-verification, or onboarding flow needs to be exercised in a real browser — verifying a new sign-up still works after a frontend/auth change, reproducing a "user stuck on onboarding" bug, checking the verification email actually arrives and its link lands the user in the right place, or wiring a smoke test for the account funnel into CI. |
Signup Flow Driver
Overview
Account funnels break in ways unit tests miss: a redirect fires before the
session cookie is set, the verification email link points at the wrong
environment, an onboarding step silently 500s for brand-new accounts. The only
way to know the funnel works is to walk it like a user — in a real browser,
against a real mailbox.
This skill drives signup → email verification → onboarding headlessly with
Playwright. It asserts observable state at every step (URL, cookie, DOM, mailbox
contents), captures a screenshot per step, and records a video plus a Playwright
trace so a failure is debuggable after the fact instead of "it was red on CI."
The discipline that makes this worth more than a hand-click: every step ends
with a programmatic assertion, not a sleep. If the assertion can't be made
true within its timeout, the run fails loudly at the step that broke.
When to Use
Reach for this when:
- A PR touches signup, auth, session handling, the verification email, or any
onboarding step, and you need proof the whole funnel still completes.
- Someone reports "I signed up but never got the email" or "onboarding won't let
me past step 2" — reproduce it deterministically here.
- You're adding a funnel smoke test to CI and need state assertions, not just a
200 from the signup endpoint.
- The verification link's target environment is suspect (link points at prod
from a staging signup, or vice versa).
Do NOT use this when:
- You only need to assert backend behavior with no UI — call the signup API and
inspect the DB directly; a browser is overhead.
- The mailbox you'd poll is a real human inbox. This needs a catch-all / test
mailbox API (Mailosaur, Mailpit, MailSlurp, or your internal test-mail
service). Never point it at a real user's email.
- You're load-testing. This drives one account at a time and is deliberately
slow and assertive.
Running it
cd .claude/skills/signup-flow-driver
pip install playwright httpx && playwright install chromium
export SIGNUP_BASE_URL="https://staging.example.com"
export MAILBOX_API_BASE="https://api.testmail.example.com"
export MAILBOX_API_KEY="..."
export TEST_EMAIL_DOMAIN="inbox.testmail.example.com"
python scripts/driver.py
Artifacts land in artifacts/<run-id>/:
01-landing.png … NN-done.png — one screenshot per step
video.webm — full session recording
trace.zip — open with playwright show-trace artifacts/<run-id>/trace.zip
result.json — machine-readable pass/fail per step, plus the test email used
Exit code is non-zero if any step assertion fails, so it drops straight into CI.
How the driver is structured
Each step is a small async function that acts, waits for a concrete condition,
then asserts. The shared pattern:
- Generate a unique test email up front (
signup+<uuid>@<TEST_EMAIL_DOMAIN>)
so parallel runs and reruns never collide and cleanup can target exactly the
accounts this run created.
- Drive the form, then
expect(page) / wait_for_url on the post-condition
— never a fixed sleep.
- After the network settles, read the thing that proves the step worked (a
cookie, a DOM node, a mailbox message) and
assert on it.
- Screenshot. Append the step result to
result.json.
Email verification is its own beast: signup triggers an async send, so the
driver polls the mailbox API for a message to the test address, with a
deadline (default 60s) and a clear failure if nothing arrives. It extracts the
verification link from the email body, then navigates the browser to it — rather
than guessing the URL — so the test exercises the real link the user would click.
Cleanup runs in a finally block: it calls the account-teardown endpoint (or
DB helper) for the exact email generated, so a failed run mid-flow doesn't leave
orphaned accounts that poison the next run's uniqueness checks.
Gotchas
- Flaky email delivery is the #1 false negative. Mail can take 2–30s in test
environments. Poll the mailbox API on an interval with a generous deadline;
never
sleep(5) and assume it arrived. When it times out, dump the mailbox's
full message list into result.json — half the time the mail did arrive but
to a slightly different address than you computed.
- Signup endpoints rate-limit. Hammering signup (CI matrix, reruns) trips
per-IP or per-domain throttles and you'll get a misleading "signup form
broken" when it's actually a 429. Detect the 429/limit response explicitly and
fail with that message, not a generic timeout. Space out reruns or use the
test-mode bypass header if your backend has one.
- Unique email per run, every run. Reusing a fixed address means the second
run hits "account already exists" and you misdiagnose it as a regression. The
+<uuid> subaddress (or a per-run mailbox namespace from the test-mail
service) is mandatory, not a nicety.
- The redirect-vs-assertion race. After submitting signup the app often
redirects and sets a session cookie, and these don't land atomically.
Asserting the cookie immediately after
click() flakes ~10% of the time. Wait
for the destination URL first (wait_for_url), then read the cookie — the
navigation completing is your signal the session is established.
- Cleanup must run even on failure. Put teardown in
finally. Otherwise the
first failing run leaves an account behind, the next run's "email already
taken" check fails, and you chase a phantom regression. If teardown itself
can't run (account never got created), that's fine — make it idempotent.
- The verification link's environment. A staging signup whose email links to
prod (or localhost) is a real, common misconfiguration. Assert the link's
origin matches
SIGNUP_BASE_URL before navigating to it — catching this is
often the whole point of the test.
Files
scripts/driver.py — async Playwright driver. Generates a unique test email,
walks signup → verify → onboarding, polls the mailbox API for the verification
mail, asserts state per step, captures screenshots + video + trace, cleans up
the account in finally, and writes result.json. Referenced by Running
it above.