Create a brand-new Dub account using a disposable email address, verify it via the 6-digit OTP code, and land on the onboarding welcome screen.
/onboarding/welcome
/onboarding/welcome AND the page shows "Welcome to Dub" with a Get started button.Spam-folder trap: Dub's OTPs ship via Amazon SES and get flagged as spam by AgentMail. The lib/agentmail.ts helper was patched to default includeSpam:true; without it, every Dub signup attempt timed out with a misleading "INBOX EMPTY" diagnostic.
Two-step signup quirk: Dub's signup form renders email + password in one place but submits email-only first to reveal the password field. Naive "fill both then submit once" approaches fail silently.
From a freshly signed-up account at /onboarding/welcome, walk through the 5-step Dub onboarding wizard and land on the workspace links dashboard.
/<slug>/links
https://app.dub.co/<slug>/links (no /onboarding/* prefix) AND the page shows "No links yet" with the Create link CTA. The slug is stable across reloads.Hidden interstitial: the first run failed because we expected the free-plan click to land directly at /<slug>/links. Dub interposes /onboarding/success with a "Go to your dashboard" button — easy to miss from the codebase alone.
State chaining: Flow 02 starts by loading Playwright storageState saved by Flow 01. Cookies + localStorage from the post-signup state are enough to resume onboarding without re-auth.
From the empty-state workspace dashboard, open the link-builder modal, paste a destination URL, submit, and confirm a new short link appears in the list.
name="url" in DOM) AND the page body contains the destination URL's host. URL footer reads "Viewing 1-1 of 1 link".Two buttons named "Create link": the empty-state trigger and the modal's submit have identical text. Disambiguating via DOM queries is fragile; pressing Enter on the focused URL input sidesteps the problem entirely.
Keyboard shortcut from code: useKeyboardShortcut("c", () => setShowLinkBuilder(true)). Using c to open the modal is faster and matches power-user behavior.
Navigate to the Custom Domains section, add a new domain via the modal, and confirm it appears in the cards list ready for DNS configuration.
name="slug" input in DOM) AND the page body contains the domain string we entered. List footer reads "Viewing 1-1 of 1 domain".Three look-alike buttons: case-insensitive matching for "Add domain" matched the modal's TRIGGER button (capital "Add Domain") instead of the form submit (lowercase "Add domain"). The first run silently no-op'd — clicking the trigger while the modal was already open did nothing. Fix: drop the /i regex flag. Case is the disambiguator Dub chose.
"Invalid" status is the expected end state: the test never configures actual DNS records, so the domain stays in the "registered but not verified" half-state. The assertion is "form succeeded + domain appears in list" — not "verified".
Navigate to the Analytics view, confirm all chart sections render even at zero traffic, and verify the page is interactive by opening the date-range filter.
clicks, devices, locations, referrers, top links AND the date-range toggle opens its popover (popover reveals Last 7 days, Last 30 days, Month to Date, etc).Read-only flow: no state mutation. dub-after-view-analytics.json is saved but mostly identical to the input state.
Why click a date-range toggle: the page-load assertion only proves the page didn't crash. Opening the popover proves the React app is hydrated and event handlers are wired up — a real reactivity check.