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
- P1 must be logged into Calendo. Open https://calendo.dev/dashboard/ — you should land on the dashboard with the sidebar visible (the "Routing Forms" tab is present,
a[data-tab="routing-forms"]). If you are bounced to https://calendo.dev/auth/login.html, STOP and FLAG this as a precondition failure per00-setup-preconditions.md. Do NOT attempt a cold email/password or Google login. - The host needs at least the TWO event types this suite creates (created in Steps 2–3). If you prefer to reuse existing event types instead of creating new ones, do NOT — always create fresh RUNID-scoped event types so analytics and routing assertions are unambiguous and reruns never collide.
- A second browsing context with NO Calendo session is required for the public-submit steps (use an incognito/private window or a separate browser profile). If you cannot open an anonymous context, FLAG it — do not submit the public form while logged in as P1, because that would still work but is not a faithful anonymous test.
- Baseline availability is irrelevant to this suite (the flow stops before slot selection), so no availability setup is required. If the matched event type happens to show zero bookable slots, that is fine — the route assertion is "the booking page for the correct event type loaded", not "a slot was booked".
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:
- Event type A (the "small" route target):
- Name:
CU12 Small Biz Call <RUNID>→ slug auto-derives tocu12-small-biz-call-<RUNID>(read the actual generated slug from the overview / event-type card; do not assume). - Duration: 30 min.
- Name:
- Event type B (the "enterprise" route target):
- Name:
CU12 Enterprise Demo <RUNID>→ slugcu12-enterprise-demo-<RUNID>. - Duration: 30 min.
- Name:
- Routing form:
- Form Name:
CU12 Lead Router <RUNID> - Slug:
cu12-lead-router-<RUNID> - Field 1 (radio, required): Label
Company size, optionsSmall,Enterprise. - Field 2 (text, optional): Label
What do you need help with. - Rule 1 (evaluated FIRST): IF
Company sizeequalsEnterprise→ route to Event type B (CU12 Enterprise Demo <RUNID>). - Rule 2 (evaluated SECOND): IF
Company sizeequalsSmall→ route to Event type A (CU12 Small Biz Call <RUNID>).
- Form Name:
- HOST_SLUG: read from the dashboard overview booking link (
#bookingLinkValue— its text ishttps://calendo.dev/booking/?user=<HOST_SLUG>; take the part afteruser=). Record it; you will build the public routing URL from it.
Steps
Part A — Read host slug & create the two route-target event types
- 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 readshttps://calendo.dev/booking/?user=<HOST_SLUG>. Record<HOST_SLUG>. [L1]- Capture screenshot:
cu12-01-dashboard-slug
- Capture screenshot:
- Action: On the Overview tab, click the "+ New" event-type button (
#overviewCreateEtBtn). In the modal that opens (#editEtModal), fill the name field (#editEtName) withCU12 Small Biz Call <RUNID>, set duration (#editEtDuration) to30, then click the Create button (#editEtSaveBtn, label reads "Create" in create mode). Expect: Modal closes; the overview event-types list (#overviewEventTypes) now containsCU12 Small Biz Call <RUNID>. [L1]
- Action: Click "+ New" again (
#overviewCreateEtBtn), fill#editEtNamewithCU12 Enterprise Demo <RUNID>, duration30(#editEtDuration), click Create (#editEtSaveBtn). Expect: Overview (#overviewEventTypes) now lists BOTHCU12 Small Biz Call <RUNID>andCU12 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.
- Capture screenshot:
Part B — Build the routing form (fields + conditional rules)
- 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-formsgains classactive); you see the heading "Routing Forms" and a "+ New Routing Form" button (#createRfBtn). [L1]
- Action: Click "+ New Routing Form" (
#createRfBtn). Expect: The create form panel appears (#rfFormPanelbecomes visible), titled "Create Routing Form", with the submit button reading "Create" (#rfSubmitBtn). [L1]
- Action: Fill Form Name (
#rfName) withCU12 Lead Router <RUNID>and Slug (#rfSlug) withcu12-lead-router-<RUNID>. Expect: Both inputs hold the typed values. [L1]
- 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"]) withCompany size; in the type dropdown (the<select>in the row) chooseRadio; check the "Required" checkbox (input[type="checkbox"]in the row). After choosing Radio, a second input appears (input[placeholder="Options (comma-separated)"]) — fill it withSmall,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.
- Action: Click "+ Add Field" again (
#rfAddFieldBtn). In the new (second) row of#rfFieldsList, fill the Label (input[placeholder="Label"]) withWhat do you need help with; leave the type as the defaultText; leave Required unchecked. Expect:#rfFieldsListnow has two field rows. [L1]- Capture screenshot:
cu12-03-fields-built
- Capture screenshot:
- 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 toCompany size; leave the operator onequals; typeEnterpriseinto the Value input. Then in the rule's "Route to:" dropdown (the event-type select at the bottom of the rule block), selectCU12 Enterprise Demo <RUNID>. Expect: Rule 1 reads: Company size equalsEnterprise→ Route toCU12 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 theinput[placeholder="Value"].
- Selector hint: within the rule block there are multiple
- Action: Click "+ Add Rule" again (
#rfAddRuleBtn). In the new "Rule 2" block: set the condition Field dropdown toCompany size, operatorequals, Value (input[placeholder="Value"]) =Small, and "Route to:" =CU12 Small Biz Call <RUNID>. Expect:#rfRulesListshows two rule blocks; Rule 2 reads: Company size equalsSmall→ Route toCU12 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
- Action: Click the submit button (
#rfSubmitBtn, label "Create"). Expect: A toast "Routing form created" appears; the create panel hides (#rfFormPanelno longer visible); and the routing forms list (#routingFormsList) now shows a card with nameCU12 Lead Router <RUNID>, a "2 fields, 2 rules" subtitle, and a green "Active" badge. [L1]- Capture screenshot:
cu12-05-form-created
- Capture screenshot:
- Action (persistence check): Reload https://calendo.dev/dashboard/, navigate to the Routing Forms tab again (
a[data-tab="routing-forms"]). Expect: The cardCU12 Lead Router <RUNID>is still present after reload with "Active" badge and "2 fields, 2 rules" — confirming it persisted. [L2]
- Action: On the
CU12 Lead Router <RUNID>card, click "Copy Link" (the button callingcopyRoutingLink(...)). Expect: A toast "Routing form link copied" appears. The copied URL ishttps://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)
- 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 exactlyCU12 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
- Capture screenshot:
- 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]
- 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) readingCU12 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).
- Capture screenshot:
Part D — Anonymous public submit #2 (Small → Event type A, different route)
- 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 titleCU12 Lead Router <RUNID>and the "Continue" button. [L1]
- 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]
- 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) readsCU12 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
- Capture screenshot:
- 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
- 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 forCU12 Lead Router <RUNID>. It is no longer the empty "No routing form submissions yet." state. [L2]
- Action: Read the analytics line for
CU12 Lead Router <RUNID>(#routingAnalytics). Expect: It reads2 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.
- Action: Read the per-event-type breakdown line under the same analytics card (
#routingAnalytics, theby_event_typeline). Expect: It contains bothCU12 Enterprise Demo <RUNID>: 1andCU12 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 intorouting_form_submissionswith the matched event type), and that the breakdown joins submissions to the right event type.
- Capture screenshot:
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:
- 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. - Delete both event types. Go to Overview / Event Types, and for each of
CU12 Enterprise Demo <RUNID>andCU12 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. - No bookings were created, so there is nothing to cancel. No throwaway account, polls, or calendar events were created by this suite.
- 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:
- [L1] Both event types
CU12 Small Biz Call <RUNID>andCU12 Enterprise Demo <RUNID>were created and appear on the overview (Steps 2–3). - [L1] The routing form
CU12 Lead Router <RUNID>was created with 2 fields and 2 rules and shows an "Active" badge (Step 11). - [L2] The form persisted across a dashboard reload (Step 12).
- [L1] The anonymous public form loaded at
/routing/?user=<HOST_SLUG>&form=cu12-lead-router-<RUNID>with the correct title and a "Continue" button (Step 14). - [L1] Submitting "Enterprise" redirected to the booking page for
cu12-enterprise-demo-<RUNID>with sidebar nameCU12 Enterprise Demo <RUNID>(Step 16). - [L1] Submitting "Small" redirected to the booking page for
cu12-small-biz-call-<RUNID>with sidebar nameCU12 Small Biz Call <RUNID>(Step 19) — i.e. a DIFFERENT route from the Enterprise submit. - [L1] (If Step 20 run) A required-field error blocked submission when Company size was left blank.
- [L2] Routing analytics for
CU12 Lead Router <RUNID>shows submissions ≥ 2 with0 booked / 0% conversion(Step 22). - [L2] The per-event-type breakdown shows
CU12 Enterprise Demo <RUNID>: 1ANDCU12 Small Biz Call <RUNID>: 1(Step 23).
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
cu12-01-dashboard-slug— dashboard overview showing the host booking link / slug.cu12-02-two-event-types— overview with both<RUNID>event types.cu12-03-fields-built— routing form builder with both fields.cu12-04-rules-built— routing form builder with both rules (Enterprise→B, Small→A).cu12-05-form-created— the saved routing form card (Active, "2 fields, 2 rules").cu12-06-public-form-anon— the public routing form loaded in the anonymous context.cu12-07-route-enterprise— booking page after Enterprise submit (URL + sidebar event name visible).cu12-08-route-small— booking page after Small submit (URL + sidebar event name visible).cu12-09-analytics— the Conversion Analytics block showing "2 submissions · 0 booked · 0% conversion" and the per-event-type breakdown.- Note: record the exact final analytics text string and both redirect URLs verbatim into the results report.
Manual residue / cannot-verify
- End-to-end conversion (booked count). This suite deliberately stops at the booking page, so the analytics "booked" / "conversion %" path (which requires actually confirming a booking that traces back to a routing submission via
booking_id) is NOT exercised here. If a human wants to verify conversion attribution, they must complete a booking from the routed booking page and re-check that the form'sconversionsincrements — out of scope for CU-12. - "No matching event type" 404 path. The server returns a 404 ("No matching event type found for your answers") and the public page shows an error when an answer matches no rule. The form built here has rules covering both radio options, so this path cannot be hit without editing the form to add an unrouted option or removing a rule. Hand off to a human or a dedicated negative-path test if explicit coverage is wanted.
containsoperator and multi-condition rules. This suite only exercises theeqoperator with single-condition rules. Thecontainsoperator (case-insensitive substring on text answers) and rules with multiple AND-ed conditions are real features in the code but are not covered here; flag for a follow-up test if needed.- Inactive form behavior. The public endpoint only serves forms with
is_active = 1. Toggling a form inactive and confirming the public URL 404s is not covered here. - Clipboard read of the copied link. The agent should construct PUBLIC_ROUTING_URL from the known slug rather than depending on reading the OS clipboard; if clipboard read is unavailable, that is expected and not a failure.