Files

11 KiB

name, description, triggers
name description triggers
browser-signup-automation Browser-driven account and referral signup automation using SeleniumBase/Playwright with temp mail, OTP polling, anti-bot fallbacks, and deterministic verification. browser signup|browser registration|selenium signup|otp signup|referral bot|vercel signup|v0 referral

Browser Signup Automation

Use when

  • The task is browser-based account creation or referral completion.
  • The flow requires a real browser, JavaScript, redirects, OTP/email verification, or anti-bot handling.
  • API-only registration is unavailable or incomplete.

Do not use when

  • A fully documented HTTP/API signup path exists and is simpler.
  • The task is ordinary account access/checkouts rather than new registration.

Alternative: API-first registration with 2captcha Turnstile bypass

When the browser approach is blocked by Cloudflare Turnstile (CDP/DevTools detection), switch to:

  1. Solve Turnstile via 2captcha (not browser click) — submit sitekey + pageurl to 2captcha in.php?method=turnstile, poll res.php for the token.
  2. Find the server-side turnstile validation endpoint — look for patterns like turnstile-signup, captcha/verify, token/validate in the site's JavaScript bundles or network logs.
  3. Call the validation endpoint with {email, token} — if it returns 204 No Content or similar, the token is accepted server-side.
  4. Complete registration via the actual auth API — Firebase REST (identitytoolkit.googleapis.com/accounts:signUp), Auth0, or custom API.

Identifying the signup API from JS bundles

  • Load modulepreloads listed in the page's HTML link[rel="modulepreload"].
  • Check the importmap (script[type="importmap"]) for hash resolution — old hashes in JS imports map to current hashes.
  • Focus on files named signup*, LoginSignupForm*, _auth*, and auth* — these contain the registration component.
  • Search for server-function calls (/_serverFn/<hash>) and external API calls (https://api.example.com/auth/) in the bundle text.
  • Trace the Turnstile widget component — typically rendered with siteKey, action, onTokenChange props. The action string (e.g. signup_email_password) must match when solving via 2captcha.
  • See references/turnstile-api-first-registration.md for session-specific examples (SolidStart server functions, Firebase Auth integration).

Turnstile CDP Detection Facts

  • Turnstile with implicit rendering (the checkbox "Verify you are human") detects Chrome DevTools Protocol reliably. Clicking the checkbox via CDP always results in failure_retry → "Verification failure".
  • Setting the hidden cf-turnstile-response input value via JS is NOT enough — the form framework checks window.turnstile.getResponse() internally, which returns empty.
  • To properly inject a 2captcha-solved token there are three options, in order of preference:
    • (a) Stub window.turnstile BEFORE api.js loads and let the app call you. Serve your own JS from page.route("**/challenges.cloudflare.com/turnstile/**", fulfill(stub)) plus add_init_script(stub); the stub's render(el, opts) immediately invokes opts.callback(TOKEN) (and opts["before-interactive-callback"]() first). The framework's own state updates, the submit button unlocks, and the page sends the request itself with every native header it normally adds. This is the only option that works when the endpoint also requires client-side attestation you cannot reproduce (WorkOS Radar signalsId, request-correlation ids, fingerprint headers) — hand-fetching such an endpoint returns 403 (request_unverified / Cloudflare WAF challenge) even with a perfectly valid token.
      • The token must be literal text inside the stub (STUB.replace("__TOKEN_PLACEHOLDER__", json.dumps(tok))), never a global assigned by a separate init script: the fulfilled api.js runs in a context the earlier assignment does not reach. Symptom of getting this wrong is silent — stub installed, render called, button stays disabled forever. Put console.log lines in the stub and watch them via page.on("console"); the DOM looks identical either way.
      • Full worked example with probe logs: ai-provider-autoreg → references/workos-turnstile-native-submit.md.
    • (b) call the Turnstile success callback directly on a real widget (needs the widget to have rendered and a reachable widgetId).
    • (c) find the server-side endpoint that accepts the raw token (bypasses widget state entirely) — viable only when the backend does not demand browser attestation alongside the token.
  • Pass the widget's exact action to the solver (from the app's actions enum, e.g. password_signup). A valid token solved with an empty/mismatched action is rejected as unverified.
  • If one solver's tokens are consistently rejected, switch solver before rewriting the payload — a dead/internal error solver looks identical to a wrong payload from the outside.
  • Temp-mail domains are often blocked server-side. Check for a blocked-domain Set in the registration JS bundle — it may contain hundreds of entries.

Pitfalls

  • Browser automation can appear to "work" because the email field fills and button clicks, while the form never transitions. Treat that as failure, not progress.
  1. Hardcode or pass the target referral/signup URL explicitly; avoid blank defaults that require manual menu input.
  2. Prefer a CLI entrypoint over an interactive menu for unattended runs.
  3. Create temp mail first, then launch the browser instance.
  4. Navigate explicitly after browser activation; do not assume CDP activation already opened the target URL.
  5. Verify navigation left about:blank before waiting for page elements.
  6. Wait for the email field using direct DOM/JS polling first; treat slow framework default timeouts as a bug source.
  7. After filling email, submit the form in layers:
    • visible button click
    • fallback alternate text button
    • form.requestSubmit()/form.submit()
    • Enter key dispatch as last resort
  8. Only wait for OTP after confirming the UI truly left the email-entry step.
  9. Distinguish these states explicitly:
    • still on email form
    • OTP screen visible
    • redirected to final destination
    • blocked/challenged/error state
  10. Poll mail only after the OTP UI is real; otherwise you waste time on emails that may never be sent.
  11. Keep final verification strict: success means redirected/landed on the target destination, not just that email was submitted.

Implementation rules

  • For SeleniumBase + argparse scripts, do NOT inject sys.argv.append("-n"); it can break CLI parsing with unrecognized arguments: -n.
  • Expose --ref, --workers, --headless, and optional --menu so the script can run unattended.
  • Add a top-level HEADLESS flag and map it consistently into the browser constructor.
  • In Chrome args for server-like runs, include --no-sandbox and --disable-gpu.
  • Prefer bulk type() or JS value assignment over slow per-key typing for email/OTP fields.
  • When clicking submit buttons is flaky, submit the closest form via JS before trying synthetic Enter.

Pitfalls

  • Browser automation can appear to "work" because the email field fills and button clicks, while the form never transitions. Treat that as failure, not progress.
  • Gmail +aliases detected by some sites: Token Harbor and similar platforms strip the +alias part from Gmail addresses, detecting user+th1@gmail.com and user+th2@gmail.com as the same account. Use different email providers (t-online.de pool) or completely different base emails for multi-account registration.
  • API key created ≠ API key works: Some platforms allow API key creation without email verification, but the key returns email_not_verified on API calls. Always test the key with a real API call before declaring success.
  • $5/credit bonuses require verification first: click the bonus claim button AFTER email verification, not before.
  • Signup credit can be granted asynchronously after a fraud review — do not conclude "no bonus" from a fresh account. On abliteration.ai, /api/console/v1/rewards returned eligible:false, status:"review_required", signup_credit_usd_micros:0 right after verify on 4 of 5 accounts, then eligible:true, status:"available", signup_credit_usd_micros:1000000 (= $1) on one account ~45 min later; only that account's key passed a real chat completion (others: insufficient_credits). Rule: read the rewards/credits API rather than the pricing page, and re-poll older accounts before judging yield. Note marketing pages reuse the same figure for unrelated things — the "$1" there was the minimum prepaid top-up.
  • Provisioning behind the signup can be async too. A console/session endpoint may answer 409 workspace_setup_pending for minutes after a successful verify; poll it (5s intervals, generous cap) before declaring failure, and keep the account in a re-try list so a later pass can still collect its key.
  • Temp-mail acceptance is target-specific. If the page never transitions after submit, suspect temp-mail rejection or hidden anti-bot checks.
  • The temp-mail API may ignore the address you ask for. Voidash (POST /api/v1/inboxes {"domain":...}) accepts address/local_part in the body and then hands back its own random address; registering your invented name sends the OTP to a mailbox you do not hold, so the flow dies silently at "waiting for code". Always take the address from the create response and register that. Also confirm which read endpoint actually works for the session key (GET /api/v1/messages did; /api/v1/inboxes/messages → 404 on a fresh inbox).
  • A verification-code form may auto-submit once the code is complete. Clicking its button then races the framework and dies with TimeoutError ... waiting for element to be enabled while it is aria-busy. Set the value with the native setter, poll for the network response, and click only as a fallback.
  • OAuth buttons sit above the password form on many sign-in pages. locator("button[type=submit]").first hits "Continue with Google" and the session ends up on accounts.google.com. Scope the click: document.querySelector('input[name="password"]').closest('form') → its own submit button, then assert page.url has no accounts.google.com.
  • A validation error body often names the missing field. 422 {"param":"header.Idempotency-Key"} told us exactly which header to add (one UUID → 201). Read param/field/code before probing endpoint variants — probes burn IP rate-limit budget.
  • Driver bootstrap/download noise can mask the real application logs; wait past first-run driver install before diagnosing the flow.
  • A running browser process is not success; require OTP screen or final redirect.

Verification checklist

  • Browser opened the exact target URL.
  • Email field became visible and value matched expected address.
  • Submit action changed page state.
  • OTP screen appeared OR an explicit block/error was captured.
  • Final URL matched the intended destination.

Support files

  • references/vercel-v0-referral-signup.md — Vercel/v0-specific quirks, observed failures, and launch defaults.