CU-12: Routing forms: build → public submit → route to event type → analytics

Priority: P2 Accounts/sessions: P1 host (ravikantguptaofficial@gmail.com, already signed into Calendo at https://calendo.dev/dashboard/) + one anonymous/incognito browser context (no Calendo session) Parallel-safe: Yes — this suite only creates its own RUNID-scoped event types and routing form; it never edits global availability and never books real meetings. Exclusive (rewrites global host availability?): No Estimated time: 18 minutes L3 reality checks: None. This suite has no email or external-calendar side effects (no booking is ever confirmed — the flow stops at the booking page, which does not create a calendar event or send mail). All assertions are [L1] UI and [L2] Calendo persistence/analytics.

Goal

This suite proves Calendo's "routing forms" feature end to end: a host can build a qualifier form (questions + field types + conditional routing rules) in the dashboard, publish a public anonymous URL, and have invitees answer questions that deterministically route them to the correct event type's booking page. It also proves the routing analytics roll-up (submission count, conversions, per-event-type breakdown) increments correctly. This matters because routing forms are how multi-offering hosts (e.g. "Small business" vs "Enterprise" leads) funnel the right invitee to the right meeting type instead of guessing — a wrong route silently sends a prospect to the wrong call, so correctness here is load-bearing for the host's funnel.

Preconditions

Test data

Pick a fresh RUNID at execution time, e.g. a UTC timestamp 20260601-1530. Embed it in every created name/slug so reruns never collide. Create exactly:

Steps

Part A — Read host slug & create the two route-target event types

  1. Action: Go to https://calendo.dev/dashboard/ and wait for the dashboard to finish loading (sidebar visible, #appLayout). On the Overview tab, read the booking link text (#bookingLinkValue). Expect: It reads https://calendo.dev/booking/?user=<HOST_SLUG>. Record <HOST_SLUG>. [L1]
    • Capture screenshot: cu12-01-dashboard-slug
  1. Action: On the Overview tab, click the "+ New" event-type button (#overviewCreateEtBtn). In the modal that opens (#editEtModal), fill the name field (#editEtName) with CU12 Small Biz Call <RUNID>, set duration (#editEtDuration) to 30, then click the Create button (#editEtSaveBtn, label reads "Create" in create mode). Expect: Modal closes; the overview event-types list (#overviewEventTypes) now contains CU12 Small Biz Call <RUNID>. [L1]
  1. Action: Click "+ New" again (#overviewCreateEtBtn), fill #editEtName with CU12 Enterprise Demo <RUNID>, duration 30 (#editEtDuration), click Create (#editEtSaveBtn). Expect: Overview (#overviewEventTypes) now lists BOTH CU12 Small Biz Call <RUNID> and CU12 Enterprise Demo <RUNID>. [L1]
    • Capture screenshot: cu12-02-two-event-types
    • Why order matters: the routing rules' "Route to" dropdown is populated from the host's existing event types, so both must exist before you build the form.

Part B — Build the routing form (fields + conditional rules)

  1. Action: In the left sidebar, click the "Routing Forms" tab (.sidebar-nav a[data-tab="routing-forms"]). Expect: The Routing Forms panel becomes active (#panel-routing-forms gains class active); you see the heading "Routing Forms" and a "+ New Routing Form" button (#createRfBtn). [L1]
  1. Action: Click "+ New Routing Form" (#createRfBtn). Expect: The create form panel appears (#rfFormPanel becomes visible), titled "Create Routing Form", with the submit button reading "Create" (#rfSubmitBtn). [L1]
  1. Action: Fill Form Name (#rfName) with CU12 Lead Router <RUNID> and Slug (#rfSlug) with cu12-lead-router-<RUNID>. Expect: Both inputs hold the typed values. [L1]
  1. Action: Click "+ Add Field" (#rfAddFieldBtn). A field row appears as the first child of #rfFieldsList. In that row: fill the Label input (input[placeholder="Label"]) with Company size; in the type dropdown (the <select> in the row) choose Radio; check the "Required" checkbox (input[type="checkbox"] in the row). After choosing Radio, a second input appears (input[placeholder="Options (comma-separated)"]) — fill it with Small,Enterprise. Expect: The row shows Label = Company size, type = Radio, Required checked, options = Small,Enterprise. [L1]
    • Why: choosing the type to "Radio" re-renders the row and reveals the options input; the order (type → then options) is required because the options input only exists for select/radio types.
  1. Action: Click "+ Add Field" again (#rfAddFieldBtn). In the new (second) row of #rfFieldsList, fill the Label (input[placeholder="Label"]) with What do you need help with; leave the type as the default Text; leave Required unchecked. Expect: #rfFieldsList now has two field rows. [L1]
    • Capture screenshot: cu12-03-fields-built
  1. Action: Click "+ Add Rule" (#rfAddRuleBtn). A rule block appears as the first child of #rfRulesList, labeled "Rule 1" with one condition row. In that condition row there are three controls: a Field dropdown, an operator dropdown (equals/contains), and a Value text input (input[placeholder="Value"]). Set the Field dropdown to Company size; leave the operator on equals; type Enterprise into the Value input. Then in the rule's "Route to:" dropdown (the event-type select at the bottom of the rule block), select CU12 Enterprise Demo <RUNID>. Expect: Rule 1 reads: Company size equals Enterprise → Route to CU12 Enterprise Demo <RUNID>. [L1]
    • Selector hint: within the rule block there are multiple <select> elements. The condition's Field select is the first select, the operator select is the second, and the "Route to" event-type select is the third (the Playwright spec selects them as .nth(0) field, .nth(2) target). The Value is the input[placeholder="Value"].
  1. Action: Click "+ Add Rule" again (#rfAddRuleBtn). In the new "Rule 2" block: set the condition Field dropdown to Company size, operator equals, Value (input[placeholder="Value"]) = Small, and "Route to:" = CU12 Small Biz Call <RUNID>. Expect: #rfRulesList shows two rule blocks; Rule 2 reads: Company size equals Small → Route to CU12 Small Biz Call <RUNID>. [L1]
    • Why rule order matters: the engine evaluates rules top-to-bottom and the FIRST matching rule wins. The two rules here are mutually exclusive (Enterprise vs Small), so order does not change the outcome — but you are verifying the documented "first match wins" semantics, so keep Enterprise as Rule 1.
    • Capture screenshot: cu12-04-rules-built
  1. Action: Click the submit button (#rfSubmitBtn, label "Create"). Expect: A toast "Routing form created" appears; the create panel hides (#rfFormPanel no longer visible); and the routing forms list (#routingFormsList) now shows a card with name CU12 Lead Router <RUNID>, a "2 fields, 2 rules" subtitle, and a green "Active" badge. [L1]
    • Capture screenshot: cu12-05-form-created
  1. Action (persistence check): Reload https://calendo.dev/dashboard/, navigate to the Routing Forms tab again (a[data-tab="routing-forms"]). Expect: The card CU12 Lead Router <RUNID> is still present after reload with "Active" badge and "2 fields, 2 rules" — confirming it persisted. [L2]
  1. Action: On the CU12 Lead Router <RUNID> card, click "Copy Link" (the button calling copyRoutingLink(...)). Expect: A toast "Routing form link copied" appears. The copied URL is https://calendo.dev/routing/?user=<HOST_SLUG>&form=cu12-lead-router-<RUNID>. Record this PUBLIC_ROUTING_URL (you can also construct it directly from <HOST_SLUG> and the form slug rather than relying on clipboard read). [L1]

Part C — Anonymous public submit #1 (Enterprise → Event type B)

  1. Action: In the ANONYMOUS browser context (no Calendo session), open the PUBLIC_ROUTING_URL: https://calendo.dev/routing/?user=<HOST_SLUG>&form=cu12-lead-router-<RUNID>. Expect: The form loads (loading spinner disappears). The title (#form-title) reads exactly CU12 Lead Router <RUNID>. You see a radio group for "Company size" with options "Small" and "Enterprise", a text field "What do you need help with", and a submit button reading "Continue" (#btn-submit). [L1]
    • Capture screenshot: cu12-06-public-form-anon
  1. Action: Select the radio option "Enterprise" (input[value="Enterprise"]). Optionally type anything in the text field. Click "Continue" (#btn-submit). Expect: The form view hides and a "Finding the right meeting type..." result view (#result-view) shows briefly, then the browser is redirected. [L1]
  1. Action: Wait for the redirect to complete. Expect: The URL is now https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu12-enterprise-demo-<RUNID> and the booking page renders with the event name in the sidebar (#sidebar-event-name) reading CU12 Enterprise Demo <RUNID>. This is the CORRECT route for an Enterprise answer. [L1]
    • Capture screenshot: cu12-07-route-enterprise
    • Stop here — do NOT pick a slot or confirm a booking. The route assertion is complete once the correct event-type booking page loads. Booking is out of scope for this suite (and would create real side effects).

Part D — Anonymous public submit #2 (Small → Event type A, different route)

  1. Action: In the anonymous context, open the PUBLIC_ROUTING_URL again (fresh load): https://calendo.dev/routing/?user=<HOST_SLUG>&form=cu12-lead-router-<RUNID>. Expect: The form re-loads with title CU12 Lead Router <RUNID> and the "Continue" button. [L1]
  1. Action: This time select the radio option "Small" (input[value="Small"]) and click "Continue" (#btn-submit). Expect: Result view shows briefly, then redirect occurs. [L1]
  1. Action: Wait for redirect. Expect: The URL is now https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu12-small-biz-call-<RUNID> and the booking sidebar (#sidebar-event-name) reads CU12 Small Biz Call <RUNID>. This is a DIFFERENT route from Step 16, proving the rules discriminate by answer. [L1]
    • Capture screenshot: cu12-08-route-small
  1. Action (negative/edge check — optional but recommended): In the anonymous context, open the PUBLIC_ROUTING_URL again, but DO NOT select any Company size option, then click "Continue". Expect: The form does NOT submit — an inline error message (#form-error) appears reading "Company size" is required. because the radio field is required. (No redirect, no submission counted.) [L1]
    • Note: This verifies required-field validation. Because the only routing rules key on Company size, there is no clean "valid answer with no matching rule" case to exercise the server's "No matching event type found" path without editing the form; that 404 path is noted under Manual residue.

Part E — Verify routing analytics incremented

  1. Action: Back in the P1 (logged-in) browser, reload https://calendo.dev/dashboard/ and navigate to the Routing Forms tab (a[data-tab="routing-forms"]). Scroll to the "Conversion Analytics" section (#routingAnalytics). Expect: The analytics block now shows an entry for CU12 Lead Router <RUNID>. It is no longer the empty "No routing form submissions yet." state. [L2]
  1. Action: Read the analytics line for CU12 Lead Router <RUNID> (#routingAnalytics). Expect: It reads 2 submissions · 0 booked · 0% conversion (2 submissions = the two valid Enterprise + Small submits from Steps 15 and 18; the rejected required-field attempt in Step 20 is NOT counted because it never POSTed; 0 booked because no booking was confirmed). [L2]
    • Note on count: if you skipped the optional Step 20 the count is still 2 (only valid submits count). If you re-ran any submit, the number will be higher — that is acceptable as long as it is ≥ 2 and reflects exactly the number of valid Continue clicks you performed.
  1. Action: Read the per-event-type breakdown line under the same analytics card (#routingAnalytics, the by_event_type line). Expect: It contains both CU12 Enterprise Demo <RUNID>: 1 and CU12 Small Biz Call <RUNID>: 1 (each route was taken exactly once), separated by · . [L2]
    • Capture screenshot: cu12-09-analytics
    • Why this is the key assertion: it proves the submission was logged with the correct routed_to_event_type_id (server inserts into routing_form_submissions with the matched event type), and that the breakdown joins submissions to the right event type.

L3 reality checks

None — see Pass/Fail. This suite intentionally stops at the booking page and never confirms a booking, so there is no real Google/Outlook calendar event and no email to verify. (Conversions stay at 0 by design.) Do not open Gmail or Google Calendar for this suite.

Cleanup

Leave the host accounts clean. In the P1 dashboard:

  1. Delete the routing form. Routing Forms tab → on the CU12 Lead Router <RUNID> card click "Delete" → confirm "Delete" in the dialog (showConfirm). Expect a "Routing form deleted" toast and the card disappears. Deleting the form also removes its submissions from analytics roll-up scope, so the analytics entry vanishes.
  2. Delete both event types. Go to Overview / Event Types, and for each of CU12 Enterprise Demo <RUNID> and CU12 Small Biz Call <RUNID>, open it and delete it (use the event-type delete action; confirm any dialog). Verify both names are gone from #overviewEventTypes.
  3. No bookings were created, so there is nothing to cancel. No throwaway account, polls, or calendar events were created by this suite.
  4. Reload the dashboard and confirm Routing Forms list is back to empty (or contains no <RUNID>-scoped items) and the two <RUNID> event types are gone.

Pass/Fail criteria

The run PASSES only if ALL of the following are true:

The run FAILS if any redirect lands on the WRONG event type, if analytics did not increment, if the breakdown attributes a submission to the wrong event type, or if a precondition (P1 session) was missing and had to be improvised.

Evidence to capture

Manual residue / cannot-verify