ClientsFlow pipeline · autonomous E2E test runner
Branch feature/funnel-automation · live app matyas--clientsflow-pipeline-web.modal.run · 2026-06-20 · ZZ sentinels only
deposit_paid),
self-healing real bugs along the way. 191 pytest green throughout; deployed v051→v053. A second round
fixed a DocuSeal-sweep safety bug + ran a 20-check Phase-2 layer; a third VISUAL re-walk (reading
every screenshot, opening the post-call wizard UI) caught a real wizard-crash bug the API-only walk hid.TypeError: (s||"").replace is not a function
— the HTML-escape helper crashed on the numeric price, so the wizard never rendered for any real priced deal.
That class of bug is invisible to API-only testing. Lesson baked in: look at the screens.Each leg was driven against the real production handlers and asserted before the next.
Deal deal_2d460d341b50 — "ZZ E2E — Happy B" (matyas.sarudi00+zze2eb).
| # | Leg | How it was driven | Proof |
|---|---|---|---|
| 1 | Cold email | Real HU email matyas@clientsflow.hu → +zze2eb | Gmail msg id returned |
| 2 | Reply → New-Lead card | Real reply sent; live /missive/incoming handler driven (cold-inbox account) | Deal minted, stage new_lead, Notion CRM page created |
| 3 | Booking | /dash/api/call/schedule (GHL retired → dashboard path) | Stage booked; Google Meet + invite email delivered |
| 4 | Call / transcript | HU transcript → /dash/api/transcript → ai.extract_transcript | _extract: price 450 000 Ft, deposit 225 000, confidence high |
| 5 | Post-call wizard | Step 2 AI proposal preview → step 3 eager DocuSeal | Proposal generated; DocuSeal both sign links + Stripe link (fix) |
| 6 | Sign both | Client + rep DocuSeal embeds signed via Chrome DevTools MCP | "Document has been signed!" ×2 → signed=True, contract_signed |
| 7 | Proposal email (human gate) | /dash/api/seq/arm — the explicit operator action | proposal_chase armed; email queued into the send window (weekend-throttled, by design) |
| 8 | Stripe TEST payment | Card 4242 on the hosted checkout via Chrome DevTools MCP | "Thanks for your payment"; webhook → deposit_paid, paid=True, onboarding fired |
The runner fixed code, redeployed via snap_deploy.sh, kept the pytest suite green (now 199), and (for the funnel bug) restarted the run from scratch.
| What | Type | Root cause | Fix |
|---|---|---|---|
| Post-call wizard crashes on a numeric price | Product (UI) | Found in the VISUAL re-walk: esc=(s||"").replace(…) isn't string-safe; pcStep1 renders value="${esc(f.price)}" where the price is the number 450000 → (450000).replace → TypeError. The wizard never opens for any real priced deal. API-driving bypassed this. | esc() made string-safe: String(s==null?"":s).replace(…). v053. |
| Missing deposit Stripe link in the wizard | Product | The post-call wizard (preview→step3) never minted the deposit Stripe link — only the legacy /review page-1 did. So the DocuSeal doc, proposal email & step-3 panel all lost the payment link. | flows.ensure_deposit_stripe_link() called inside ensure_docuseal_submission() (parity with /review). v051. |
| Evidence HTML always empty | Harness | e2e_evidence.py built each step but never appended it to steps[]. | One-line append fix. |
| ZZ-DocuSeal sweep was a silent no-op | Safety | e2e_reset.py detected submissions via a board field the API never exposed → the mandated post-attempt DocuSeal archive never ran. A stale cross-container cache also made the verify lie. | Board now exposes has_docuseal; reset detects via it and reads fresh=1. v052. Verified end-to-end. |
Every screenshot re-read and compared to what its leg should show. run_003's 8 are all faithful; 6 of run_002's 9 are misleading — the concrete proof of round-1's "passed from API state, not from the screen" failure. Each fix below was driven test-first (red → green → refactor); the suite went 191 → 199 green, 0 regressions.
| Screenshot | Should show | Actually shows | Verdict |
|---|---|---|---|
run_002 / 02_newlead_card | New-Lead card | A "Loading…" board — no card at all | mismatch |
run_002 / 08_proposal_armed | Proposal armed / sign-FUP | Byte-identical to 02 (md5 de044769…) — the same Loading frame reused | mismatch |
run_002 / 03·04·05·10 | Booked / postcall / proposal+links / deposit-paid card | A generic board of unrelated ZZ fixtures — the claimed leg is absent | mismatch |
run_002 / 06·07·09 | DocuSeal client+rep signed · Stripe success | Genuine — the heavy-browser legs (QA-runner) do show their state | match |
run_003 / 01–08 | Newlead · wizard 1-3 · DocuSeal · Stripe · final board | All faithful — each shot shows the leg it claims | match |
Findings → fixes, all written test-first (8 new tests):
| Finding | Type | Fix (red → green) |
|---|---|---|
| H3 — the evidence harness banked Loading/wrong-card frames as PASS | Harness | Pure gate validate_evidence(page_text, expect) + --expect/--page-text on cmd_step force a fail when the captured page doesn't show the leg. 4 tests; CLI-smoke-proven (a "Loading…" page with --status pass banks as fail). The run_002 false-green class is now structurally impossible. |
P2 — outbound greeting first name derived 4 different ways; .split(" ")[0] collapsed to "Szia ott,"; onboarding could IndexError | Product | One tested helper flows.first_name_for_greeting(), all 4 sites routed through it (DRY / single-source). Real-name behaviour unchanged ("Kovács Anna"→"Kovács" — copy invariant respected). 2 tests. The literal "Szia ZZ" is a test-sentinel artifact, not a prod defect. |
P1 — the v053 esc() String-coercion fix | Lock | Source-level regression lock — green on the fixed file, proven to fire red against the old (s||"").replace. 2 tests. |
| P3 — the "06:12" send-window label looked anomalous | Not a bug | Code-confirmed it's the real config.SEND_WINDOW_START (default 6,12); the label is faithful. User decision: keep 06:12. No code change. |
Playwright DOM + API assertions across the funnel mechanics & scenarios (ZZ-gated, send-blocklisted). The 2 "fails" are stale tests, not product bugs.
#pc-email-draft, an element the R3 split-pane redesign retired — the
proposal-email draft now lives in the seq-editor #se-body (covered by R3B3, PASS).
B1 seeds a long inbound on S4, but S4 has a logged call-note last_touch that — by
the intended LC2 rule ("last_touch wins over last_msg") — correctly renders instead; no
truncation exists. The Playwright capture wave's S11 hit the same class of staleness (a step looking
for the "Move" button that was intentionally removed — board-card-qa A2 asserts its absence).Why some data was missing / testing was thinner than ideal — and what the second round tackled.
| Weak point | Impact | Disposition |
|---|---|---|
| "Real reply → Missive" (Q4=A) locked without verifying feasibility | The cold-outreach inbox that mints a card isn't authed on the MCP and Missive's webhook is uncontrollable → the fully-real path isn't achievable autonomously. | Drove the real production handler via the team's journey_e2e.py inject (only the Missive mailbox-sync hop substituted) + sent a genuine cold email & reply. Flagged transparently. |
| Stale "use /ghl/event for booking" guidance | handle_ghl_event is an inert stub (GHL retired). | Used the live dashboard /dash/api/call/schedule path. |
| Instance 2 (techspec) never ran — no checklist / per-leg assertion list | The runner derived legs + assertions live, discovering the Stripe-link defect by hitting it. | Legs + assertions reconstructed from the code; defect found & fixed. |
| Harness shipped "smoke-tested" but buggy (evidence append; reset DocuSeal sweep) | Empty evidence; safety sweep silently did nothing. | Both fixed & verified in this run. |
My driver mistakes (preview missing email; passed problems dicts as strings) | Two wasted wizard calls. | Corrected; not app bugs. Lesson: inspect extract field types before building inputs. |
/review page-1 path; the onboarding email/portal artifact (stage reached deposit_paid but portal:false). The 2 stale board-card-qa tests (PC4, B1) and the journey S11 "Move" step are left for the team to retire/retarget — editing the assertions just to pass would be gaming them.currency:"huf", unit_amount:225000) — the EUR figure is Stripe's Adaptive-Pricing presentment conversion (browser/locale), not a code defect. A Hungarian client would still pay the HUF-equivalent; disable Adaptive Pricing in Stripe settings to show HUF.| File | What |
|---|---|
.tmp/e2e/run_003_*.html | The VISUAL re-walk — every screen read & analyzed; found+fixed the wizard-crash bug; wizard steps 1-3 + DocuSeal doc + Stripe all visually verified |
.tmp/e2e/run_002_*.html | The green run — screen-to-screen, think-aloud, 10 steps, screenshots |
.tmp/e2e/run_001_*.html | The failed→fixed attempt (red callout at the missing Stripe-link leg) |
.tmp/journey-qa/v3_journeys_qa.html | Playwright capture wave (S10 clean; S11 stale "Move" step) |
plans/e2e-continuous-journey-test/RETROSPECTIVE.md | Full retrospective + second-round plan (§8 = this round) |
tests/test_{greeting_first_name_p2,e2e_evidence_gate_h3,esc_string_safe_p1}.py | The 8 test-first regression locks for this round's fixes (P2 · H3 · P1) |