From a365dbfb80016c6c222f6d9c856e3eb9761f4ba4 Mon Sep 17 00:00:00 2001 From: Martins Aloba Date: Sun, 17 May 2026 07:32:49 -0500 Subject: [PATCH 1/3] =?UTF-8?q?test(15-4):=20Maestro=20E2E=20skeleton=20fl?= =?UTF-8?q?ows=20=E2=80=94=205=20golden=20user=20flows=20+=20drift=20detec?= =?UTF-8?q?tor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .maestro/01-signup-flow.yaml | 43 +++++++++++++ .maestro/02-onboarding-flow.yaml | 57 +++++++++++++++++ .maestro/03-first-exercise.yaml | 52 +++++++++++++++ .maestro/04-first-conversation.yaml | 64 +++++++++++++++++++ .maestro/05-mock-test-partial-review.yaml | 59 +++++++++++++++++ .maestro/config.yaml | 15 +++++ .../sprint-status.yaml | 4 +- .../maestro-flows-source-drift.test.ts | 64 +++++++++++++++++++ 8 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 .maestro/01-signup-flow.yaml create mode 100644 .maestro/02-onboarding-flow.yaml create mode 100644 .maestro/03-first-exercise.yaml create mode 100644 .maestro/04-first-conversation.yaml create mode 100644 .maestro/05-mock-test-partial-review.yaml create mode 100644 .maestro/config.yaml create mode 100644 src/lib/__tests__/maestro-flows-source-drift.test.ts diff --git a/.maestro/01-signup-flow.yaml b/.maestro/01-signup-flow.yaml new file mode 100644 index 0000000..528ea9a --- /dev/null +++ b/.maestro/01-signup-flow.yaml @@ -0,0 +1,43 @@ +# Story 15-4 — Flow 1: Sign-up +# +# Validates: a new user can create an account and reach the +# EmailVerificationGate (Story 12-9). Subsequent flows assume an +# already-verified test account. +# +# Selectors below carry # TODO markers — operator must verify against +# the actual app via `maestro studio` before first CI wire-up. +# See _bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md. + +appId: com.companion.app +tags: + - smoke + - signup +--- +# Launch the app cold +- launchApp: + clearState: true + +# Sign-up CTA — auth screens carry an EN "Sign up" link (Story 14-1 R1-H6) +- assertVisible: "Sign up" # TODO: verify exact text matches login.tsx +- tapOn: "Sign up" + +# Email + password fields +- assertVisible: "Email address" # TODO: verify placeholder / a11y label +- tapOn: + id: "input-email" # TODO: verify testID on signup.tsx email TextInput +- inputText: "e2e-test+signup-001@invalid.localdomain" + +- tapOn: + id: "input-password" # TODO: verify testID +- inputText: "Abcdefghi1" # min 10 chars per Story 12-8 password policy + +- tapOn: + id: "input-password-confirm" # TODO: verify testID exists OR remove if single password +- inputText: "Abcdefghi1" + +# Submit +- tapOn: "Create account" # TODO: verify exact button text + +# Expect to land on EmailVerificationGate (Story 12-9) +- assertVisible: "Vérifiez votre adresse e-mail" # FR per Story 14-1 R1-Q5: gate stays EN per Q5 — verify current text +- assertVisible: "J'ai vérifié — actualiser" # TODO: verify the Refresh CTA label diff --git a/.maestro/02-onboarding-flow.yaml b/.maestro/02-onboarding-flow.yaml new file mode 100644 index 0000000..a214555 --- /dev/null +++ b/.maestro/02-onboarding-flow.yaml @@ -0,0 +1,57 @@ +# Story 15-4 — Flow 2: Onboarding wizard + placement test +# +# Prerequisite: a verified test account already exists (created via Flow 1 +# OR seeded via Supabase admin). The flow signs in with that account, then +# walks the 3-step onboarding + placement test → home. +# +# Test account credentials should live in `MAESTRO_TEST_EMAIL` / +# `MAESTRO_TEST_PASSWORD` env vars (operator-set per runbook). + +appId: com.companion.app +tags: + - smoke + - onboarding + +env: + TEST_EMAIL: "e2e-test+onboarding-001@invalid.localdomain" # TODO: replace with operator-seeded test account + TEST_PASSWORD: "Abcdefghi1" +--- +- launchApp: + clearState: true + +# Sign in (assumes account is verified) +- assertVisible: "Sign in" # TODO: verify exact text +- tapOn: + id: "input-email" # TODO: verify testID +- inputText: ${TEST_EMAIL} +- tapOn: + id: "input-password" +- inputText: ${TEST_PASSWORD} +- tapOn: "Sign in" + +# Step 1 of 3 — goals +- assertVisible: "What are your goals?" # TODO: verify exact onboarding/index.tsx text +- tapOn: "TCF preparation" # TODO: verify goal label +- tapOn: "Continue" + +# Step 2 of 3 — daily goal +- assertVisible: "Daily goal" # TODO +- tapOn: "15 min" # TODO +- tapOn: "Continue" + +# Step 3 of 3 — placement test entry +- assertVisible: "Placement test" # TODO +- tapOn: "Start placement test" # TODO + +# Placement test — answer all 15 questions (B1-ish) +- repeat: + times: 15 + commands: + - tapOn: + index: 0 # tap first option + text: ".*" + - tapOn: "Next question" # TODO: verify + +# Reach home tab +- assertVisible: "COMPANION" # home hero brand label (Story 14-1 R1-H4) +- assertVisible: "Hello" # greeting diff --git a/.maestro/03-first-exercise.yaml b/.maestro/03-first-exercise.yaml new file mode 100644 index 0000000..2be9234 --- /dev/null +++ b/.maestro/03-first-exercise.yaml @@ -0,0 +1,52 @@ +# Story 15-4 — Flow 3: Complete the first practice exercise +# +# Prerequisite: an onboarded test account exists (Flow 2 completed). +# Validates: practice tab → grammar → AI exercise generation → answer → +# grade → result + activity-tracking persists. + +appId: com.companion.app +tags: + - smoke + - exercise + +env: + TEST_EMAIL: "e2e-test+exercise-001@invalid.localdomain" # TODO + TEST_PASSWORD: "Abcdefghi1" +--- +- launchApp: + clearState: false # preserve any signed-in session + +# If not signed in, sign in +- runFlow: + when: + visible: "Sign in" + commands: + - tapOn: + id: "input-email" + - inputText: ${TEST_EMAIL} + - tapOn: + id: "input-password" + - inputText: ${TEST_PASSWORD} + - tapOn: "Sign in" + +# Navigate to Practice tab +- tapOn: "Practice" # bottom tab — Story 14-1 R1-H1 EN +- assertVisible: "Choose a skill to practice." # practice screen subtitle + +# Choose Grammar +- tapOn: "Grammar" # SkillCard label (Story 14-1) +- assertVisible: "Generating exercise" # loading state + +# Wait for the MCQ to appear (max 60s for AI gen) +- extendedWaitUntil: + visible: "Submit" # or "Check" — TODO verify the submit button label + timeout: 60000 + +# Tap first option then submit +- tapOn: + id: "mcq-option-0" # TODO: verify testID on MCQCard option Pressables +- tapOn: "Submit" + +# Expect a result screen +- assertVisible: "Correct" # ideal — TODO: handle "Incorrect" path too +- tapOn: "Next exercise" # TODO: verify CTA diff --git a/.maestro/04-first-conversation.yaml b/.maestro/04-first-conversation.yaml new file mode 100644 index 0000000..db7c9c5 --- /dev/null +++ b/.maestro/04-first-conversation.yaml @@ -0,0 +1,64 @@ +# Story 15-4 — Flow 4: First conversation (Realtime voice) +# +# Validates: conversation tab → topic → start → grant mic permission → +# session connects → user can speak (simulated) → end session. +# +# Note: Real-time voice depends on OpenAI Realtime API + microphone access. +# Maestro can simulate the mic-permission grant + the End button tap but +# CANNOT inject audio. This flow validates the SHELL — connection + UI +# state transitions — not the AI response content. + +appId: com.companion.app +tags: + - smoke + - conversation + +env: + TEST_EMAIL: "e2e-test+conversation-001@invalid.localdomain" # TODO + TEST_PASSWORD: "Abcdefghi1" +--- +- launchApp: + clearState: false + +- runFlow: + when: + visible: "Sign in" + commands: + - tapOn: + id: "input-email" + - inputText: ${TEST_EMAIL} + - tapOn: + id: "input-password" + - inputText: ${TEST_PASSWORD} + - tapOn: "Sign in" + +# Navigate to Conversation tab +- tapOn: "Conversation" # bottom tab — Story 14-1 R1 +- assertVisible: "Talk with Companion" # hero title from conversation/index.tsx + +# Pick the first topic (A1 — "Se présenter" greeting) +- tapOn: + text: "Se présenter" # FR content per Story 14-1 chrome rule + # TODO: confirm exact rendered text; topic card has both EN + FR labels + +# Start the session +- assertVisible: "Talk" # or whatever the start CTA reads — TODO +- tapOn: "Talk" + +# Grant mic permission (system dialog) +- runFlow: + when: + visible: "OK" # iOS permission dialog + commands: + - tapOn: "OK" + +# Wait for connection (status: "connected") +- extendedWaitUntil: + visible: "Companion is listening" # TODO: verify status text + timeout: 30000 + +# End the session +- tapOn: "End" # TODO: verify End button label + +# Expect feedback screen or session-summary +- assertVisible: "Session summary" # TODO: verify post-session screen diff --git a/.maestro/05-mock-test-partial-review.yaml b/.maestro/05-mock-test-partial-review.yaml new file mode 100644 index 0000000..44e510b --- /dev/null +++ b/.maestro/05-mock-test-partial-review.yaml @@ -0,0 +1,59 @@ +# Story 15-4 — Flow 5: Mock test partial completion + review +# +# Validates: mock-test tab → start TCF full simulation → complete listening +# section → submit early → results screen renders with correct CEFR + TCF +# score reconstruction (Story 14-7 + 13-4 parallel section generation). + +appId: com.companion.app +tags: + - smoke + - mock-test + +env: + TEST_EMAIL: "e2e-test+mocktest-001@invalid.localdomain" # TODO + TEST_PASSWORD: "Abcdefghi1" +--- +- launchApp: + clearState: false + +- runFlow: + when: + visible: "Sign in" + commands: + - tapOn: + id: "input-email" + - inputText: ${TEST_EMAIL} + - tapOn: + id: "input-password" + - inputText: ${TEST_PASSWORD} + - tapOn: "Sign in" + +# Navigate to Mock Test tab +- tapOn: "Mock test" # bottom tab — Story 14-1 R1 EN chrome +- assertVisible: "TCF" # mock-test/index.tsx hero brand + +# Tap the full TCF simulation card +- tapOn: "Start full simulation" # TODO: verify CTA label + +# Generation: parallel per-section (Story 13-4); wait for first section +- extendedWaitUntil: + visible: "Question 1" # TODO: verify + timeout: 120000 # 2-minute budget for section 1 generation + +# Answer listening section (29 questions per TCF Canada — answer all with index 0) +- repeat: + times: 29 + commands: + - tapOn: + id: "mcq-option-0" # TODO: verify testID + - tapOn: "Next" # TODO: verify Next button + +# Submit listening section early (won't wait for reading section to finish) +- tapOn: "Submit" # TODO: verify Submit early CTA / confirm dialog flow + +# Results screen +- extendedWaitUntil: + visible: "Results" # TODO: verify results screen header + timeout: 30000 +- assertVisible: "TCF Score" # TODO: verify +- assertVisible: "Listening" diff --git a/.maestro/config.yaml b/.maestro/config.yaml new file mode 100644 index 0000000..419679a --- /dev/null +++ b/.maestro/config.yaml @@ -0,0 +1,15 @@ +# Maestro project config — Story 15-4 +# +# Operator setup: see _bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md +# +# Run locally: +# maestro test .maestro/ +# +# Run a single flow: +# maestro test .maestro/03-first-exercise.yaml + +appId: com.companion.app + +# Tags allow selective runs (`maestro test --include-tags=smoke`). +tags: + - epic-15 diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 5405e25..8875958 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -209,8 +209,8 @@ development_status: 15-1-lib-unit-tests: done # PR #111. R1 patches: HIGH × 2 (useFakeTimers time-flake fix; dictation empty-original semantic-trap TODO + 15-1-followup filed) + MED × 3 (Case 17 distinctive-marker assertion; Case 14 ref-inequality; new Case 14a typed-preserves-case). +1 net R1 (2158 → 2159). All 5 gates green. Original: 2026-05-16: Story 15-1 implementation complete. 4 NEW test files (srs.test.ts 14 cases + pronunciation.test.ts 10 cases + cache.test.ts 20 cases + use-dictation-compare.test.ts 15 cases) = +59 net Jest cases (2099 → 2158; squarely within spec target +50-65). 0 source-module modifications — test-only story. All 5 quality gates green. CLAUDE.md paragraph added per Epic 14 retro AI #5. Original: Story 15-1 spec file created. FIRST Epic 15 story. Pure-function lib unit tests for 4 modules where direct tests don't yet exist: srs.ts (SM-2 algorithm full coverage), pronunciation.ts identifyWeakSounds (pure aggregator), cache.ts core API (getCache/setCache/invalidateCache/cacheWithFallback happy + TTL boundary), use-dictation.ts compareSentences (pure word-comparison helper). Coverage inventory completed: activity.ts already fully tested (Story 9-2 + 12-3); memory.ts + error-tracker.ts have substantial partial coverage (sanitize via 9-4 + dedup via 11-6); pronunciation.ts has history-cap test (12-12); the GAP-only scope targets the 4 untested-or-partially-tested pure-function surfaces. Async/DB-touching paths (assessPronunciation Edge wrapper, memory.ts extractFacts/persistMemories/retrieveMemories, error-tracker.ts non-dedup paths) DEFERRED to 15-1-followups or 15-2 hook-integration scope per Story 14-4 R1-22-patch lesson (broad enforcement-rule changes interact with every surface; test-writing stories at analogous risk). 3 operator-decision items Q1-Q3 all resolved per Recommended (defer Edge wrapper / memory async / error-tracker non-dedup). 0 source-module modifications — test-only story. Spec target: +50-65 net Jest cases (2099 → 2149-2164). Status: ready-for-dev. Awaiting /bmad-dev-story. 15-2-hook-integration-tests: backlog 15-3-edge-function-deno-tests: backlog - 15-4-golden-flow-e2e: backlog - 15-5-ai-schema-regression-tests: backlog + 15-4-golden-flow-e2e: in-progress # 2026-05-17: spec written. Maestro chosen per operator pre-decision. SKELETON-ONLY scope — 5 YAML flow files + runbook + drift detector. CI wiring DEFERRED (needs simulator/emulator operator-setup). + 15-5-ai-schema-regression-tests: done # 2026-05-17: PR #114 pending. NEW fixture-replay infrastructure + 3 synthetic seed fixtures + operator runbook. +8 net Jest cases. Real fixture capture deferred to operator action per runbook. 15-6-ci-coverage-gating: backlog epic-15-retrospective: optional diff --git a/src/lib/__tests__/maestro-flows-source-drift.test.ts b/src/lib/__tests__/maestro-flows-source-drift.test.ts new file mode 100644 index 0000000..3e06c55 --- /dev/null +++ b/src/lib/__tests__/maestro-flows-source-drift.test.ts @@ -0,0 +1,64 @@ +/** + * Story 15-4 — source-drift detector for Maestro E2E flow files. + * + * Pins the `.maestro/` directory structure against silent regression + * (e.g., a future PR accidentally deletes a flow file). The flows + * themselves are skeleton-only (carry `# TODO: verify selector` markers); + * full CI wiring deferred to `15-4-followup-maestro-ci-wiring`. + */ + +import * as fs from "fs"; +import * as path from "path"; + +const REPO_ROOT = path.resolve(__dirname, "../../.."); +const MAESTRO_DIR = path.join(REPO_ROOT, ".maestro"); + +const EXPECTED_FLOWS = [ + "01-signup-flow.yaml", + "02-onboarding-flow.yaml", + "03-first-exercise.yaml", + "04-first-conversation.yaml", + "05-mock-test-partial-review.yaml", +]; + +describe("Story 15-4 — Maestro flows source drift", () => { + it("Case 1: `.maestro/config.yaml` exists with the canonical appId", () => { + const configPath = path.join(MAESTRO_DIR, "config.yaml"); + expect(fs.existsSync(configPath)).toBe(true); + const content = fs.readFileSync(configPath, "utf-8"); + expect(content).toMatch(/appId:\s*com\.companion\.app/); + }); + + it("Case 2: all 5 canonical flow files exist", () => { + for (const flow of EXPECTED_FLOWS) { + const flowPath = path.join(MAESTRO_DIR, flow); + expect(fs.existsSync(flowPath)).toBe(true); + } + }); + + it("Case 3: each flow file has non-trivial body (>20 lines + appId declaration)", () => { + for (const flow of EXPECTED_FLOWS) { + const content = fs.readFileSync(path.join(MAESTRO_DIR, flow), "utf-8"); + const lineCount = content.split("\n").length; + // Skeleton flows have ~30-70 lines; pure-placeholder would be < 10. + expect(lineCount).toBeGreaterThan(20); + expect(content).toMatch(/appId:\s*com\.companion\.app/); + } + }); + + it("Case 4: each flow tags itself with `smoke` (the canonical Story 15-4 tag for golden-flow E2E)", () => { + for (const flow of EXPECTED_FLOWS) { + const content = fs.readFileSync(path.join(MAESTRO_DIR, flow), "utf-8"); + expect(content).toMatch(/^\s*-\s*smoke\s*$/m); + } + }); + + it("Case 5: NO Maestro CI step has been wired in .github/workflows/ci.yml yet (15-4 SKELETON-ONLY scope — CI wiring deferred to 15-4-followup)", () => { + const ciPath = path.join(REPO_ROOT, ".github/workflows/ci.yml"); + const ci = fs.readFileSync(ciPath, "utf-8"); + // No `maestro test` invocation should be present yet. + expect(ci).not.toMatch(/maestro\s+test\b/); + // No `get\.maestro\.mobile\.dev` install step should be present yet. + expect(ci).not.toMatch(/get\.maestro\.mobile\.dev/); + }); +}); From ac5aeedbf8498d0f9d4aca6967a3a700afa98349 Mon Sep 17 00:00:00 2001 From: Martins Aloba Date: Sun, 17 May 2026 07:32:54 -0500 Subject: [PATCH 2/3] chore(15-4): spec + runbook + CLAUDE.md + sprint-status done --- CLAUDE.md | 2 + .../15-4-golden-flow-e2e.md | 89 ++++++++++ .../sprint-status.yaml | 4 +- .../runbooks/maestro-e2e-setup.md | 166 ++++++++++++++++++ 4 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md create mode 100644 _bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md diff --git a/CLAUDE.md b/CLAUDE.md index 2bc4428..7758f62 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -201,6 +201,8 @@ A reusable pattern for capping in-memory data structures + UI work-budgets. Two **Lib unit tests — `srs.ts` SM-2 algorithm + `pronunciation.ts` `identifyWeakSounds` + `cache.ts` core API + `use-dictation.ts` `compareSentences`:** post-Epic-15.1, the FIRST Epic 15 (Test Coverage & QA Infrastructure) story closes the largest pure-function-test gap surfaced by the Story 12-10 audit refresh footnote. Per the coverage inventory at story-creation time, `activity.ts` was already fully tested (Story 9-2 + 12-3); `memory.ts` + `error-tracker.ts` had substantial partial coverage (sanitize via Story 9-4 prompt-injection.test.ts + dedup via Story 11-6 error-tracker-dedupe.test.ts + e2e clustering via 11-6); `pronunciation.ts` had history-cap coverage (Story 12-12) but NOT `identifyWeakSounds`. Story 15-1 lands **GAP-only scope** across **4 pure-function surfaces** (no source-module modifications — test-only): **(1) NEW [`src/lib/__tests__/srs.test.ts`](src/lib/__tests__/srs.test.ts)** (14 cases) pins the full SM-2 algorithm contract — 3 incorrect-response cases (quality 0/1/2 ease-factor deltas −0.80/−0.54/−0.32 + reset to `repetitions=0`, `intervalDays=1`); 3 correct-response cases (quality 3/4/5 ease-factor deltas −0.14/0/+0.10 + interval progression 1 → 6 → round(prev × ef)); 3 ease-factor 1.3-floor clamping cases (boundary + already-at-floor + just-above-floor); 3-consecutive-correct chain verification; 365-day interval cap; midnight-snap `nextReview` math via Date.now() before/after delta check; no-mutation invariant via input-snapshot equality + determinism via twice-call equivalence. **(2) NEW [`src/lib/__tests__/pronunciation.test.ts`](src/lib/__tests__/pronunciation.test.ts)** (10 cases) pins `identifyWeakSounds` contract — empty input + no-weak happy path + below-threshold-but-count<3 NOT flagged + single-phoneme-aggregated-across-results + same-phoneme-aggregated-across-words-in-one-result + threshold boundaries (`avgScore < 70` strict + `count >= 3` boundary) + multi-phoneme ranking ASCENDING by avgScore (worst first) + no-mutation deep-equal. **(3) NEW [`src/lib/__tests__/cache.test.ts`](src/lib/__tests__/cache.test.ts)** (20 cases) covers the AsyncStorage path (existing `cache-flush.test.ts` covers write-queue idempotency; existing `cache-secure-routing.test.ts` covers the Story 12-7 SecureStore fork) — `getCache` empty/fresh/TTL-fresh-boundary/TTL-expired-boundary/corrupted-JSON/throw paths each with `captureError` routing pinned; `setCache` envelope shape `{data, timestamp, ttlMs}` + DEFAULT_TTL_MS (1 hour) fallback + namespacing `@companion_cache::` (different userIds never collide); `setCache` → `getCache` round-trip with nested object structure; `invalidateCache` happy + error paths; `cacheWithFallback` 4-path matrix (fetcher-succeeds-writes-cache, fetcher-throws-stale-cache-hit-returns-fromCache:true, fetcher-throws-no-cache-rethrows-original, fetcher-throws-cache-also-throws-captures-fallback-read-error-rethrows-original); constant pins for `CACHE_KEYS` (5 keys) + `CACHE_TTL` (6 durations) + `SECURE_CACHE_KEYS` size===1 with PROFILE in / SKILLS+VOCABULARY out. **(4) NEW [`src/hooks/__tests__/use-dictation-compare.test.ts`](src/hooks/__tests__/use-dictation-compare.test.ts)** (15 cases) pins the pure word-by-word `compareSentences` helper from [`src/hooks/use-dictation.ts:88`](src/hooks/use-dictation.ts#L88) (despite living in a hook file, the function is pure — no React, no async, no side effects, so it fits 15-1 scope). Test mocks `@/src/hooks/use-audio-player` (transitive `expo-audio` native crash in Jest) + `@/src/lib/openai` (transitive supabase chain) at module load to keep the import boundary clean. Cases cover: perfect-match → 100% / one-wrong-word with `typed` populated → round(2/3 × 100)=67% / missing-word semantics (user shorter → past-user-length positions are "missing", overlap positions compare normally) / extra-word (user longer → extra tokens IGNORED; only original-length iterated) / empty-userInput → all "missing" / empty-original → empty wordResults + accuracy=0 + isFullyCorrect=true vacuously / both-empty same / case-insensitive ("Bonjour" vs "bonjour") with display preserving original capitalization / accent-insensitive ("café" vs "cafe") with display preserving accent / trailing punctuation stripped / internal comma stripped / multi-space collapses / leading+trailing whitespace trimmed / apostrophe stripped ("l'eau" matches "leau") / fresh wordResults array per call (no shared module-level cache). **3 operator decisions resolved per Recommended** (Q1 defer `assessPronunciation` Edge wrapper to 15-1-followup; Q2 defer `memory.ts` async/DB-touching paths to 15-2; Q3 defer `error-tracker.ts` non-dedup paths to 15-2) — keeps 15-1 pure-function-only per the Epic 14 retro lesson that test-writing stories are at scope-drift risk. **Explicitly out of scope** (filed for follow-up if motivated): `memory.ts` `extractFacts`/`persistMemories`/`retrieveMemories`; `error-tracker.ts` `getTopErrors`/`extractErrorsFromCorrections`/`persistErrorPatterns`/`getRecentResolvedError`; `pronunciation.ts` `assessPronunciation` Edge Function wrapper; `cache.ts` `enqueueWrite`/`flushWriteQueue` (already in `cache-flush.test.ts`)/`clearUserCache`/`clearAllCache` multi-key sweeps; `use-dictation.ts` `analyzeErrorPatterns`. **Cross-story invariants preserved by construction:** zero source-module modifications (Story 15-1 is test-only); Story 9-3 Sentry allowlist zero-diff (cache tests verify EXISTING `cache-get`/`cache-set`/`cache-invalidate`/`cache-fallback-read` feature tags fire; no new tags); Story 12-7 SecureStore fork unchanged (cache.test.ts uses non-secure key `"skills"` to exercise AsyncStorage path); all Epic 14 invariants orthogonal. **+59 net Jest cases** (2099 → 2158; spec target +50-65 — squarely in range). 4 new test files; 0 modified source files. All 5 design-system gates green (type-check 0 errors / lint 0 warnings / prettier clean / check:tokens clean / jest 113 suites / 2158 tests). Verified 2026-05-16, story 15-1. +**Golden-flow E2E Maestro skeleton flows + operator runbook:** post-Epic-15.4 ships the structural framework for end-to-end smoke testing. **Operator pre-decision at autopilot kickoff: Maestro** over Detox per the spec's recommended default (faster iteration, simpler YAML config, no Xcode/Android Studio project-level integration required). **SKELETON-ONLY scope** because Maestro requires a running simulator/emulator + built app binary + Maestro CLI installed locally — none of which are available in the autopilot session; the cleanest scope-cut is YAML flow files + operator runbook + drift detector, with CI wiring deferred to `15-4-followup-maestro-ci-wiring` after the operator sets up simulator/emulator infrastructure on a dedicated runner. NEW `.maestro/config.yaml` (declares `appId: com.companion.app`) + 5 YAML flow files covering the spec's golden flows: [`.maestro/01-signup-flow.yaml`](.maestro/01-signup-flow.yaml) (sign-up → Story 12-9 EmailVerificationGate); [`.maestro/02-onboarding-flow.yaml`](.maestro/02-onboarding-flow.yaml) (sign-in → 3-step onboarding wizard → placement test 15Q → home); [`.maestro/03-first-exercise.yaml`](.maestro/03-first-exercise.yaml) (Practice tab → Grammar → AI exercise → grade); [`.maestro/04-first-conversation.yaml`](.maestro/04-first-conversation.yaml) (Conversation tab → topic → start Realtime session → mic permission → end — validates SHELL connection + UI state transitions, NOT AI response content since Maestro can't inject audio); [`.maestro/05-mock-test-partial-review.yaml`](.maestro/05-mock-test-partial-review.yaml) (Mock Test tab → TCF full sim → listening section 29Q → submit partial → results screen with Story 13-4 parallel section generation + Story 14-7 results reconstruction). Each flow file carries `# TODO: verify selector` markers throughout — operator must verify testIDs / accessibility labels / text strings against the actual running app via `maestro studio` before first execution. Env-injected test account emails (`e2e-test+-001@invalid.localdomain` + `Abcdefghi1` password complies with Story 12-8 ≥10-char policy). NEW operator runbook [`_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md`](_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md) (7 sections) walks: install Maestro CLI → boot iOS Simulator → `npx expo run:ios` dev client → `maestro studio` selector verification → seed verified test accounts (Supabase Studio Admin OR seed script using `email_confirm: true`) → run flows locally → CI wire-up procedure for the deferred follow-up. NEW [`src/lib/__tests__/maestro-flows-source-drift.test.ts`](src/lib/__tests__/maestro-flows-source-drift.test.ts) (5 cases) pins: config.yaml with canonical appId; all 5 flow files exist; each flow has non-trivial body (>20 lines + appId); each tags itself with `smoke`; NEGATIVE — NO Maestro CI step is wired in `ci.yml` yet (15-4 skeleton-only contract; CI wiring should fail loudly when added without the 15-4-followup completing first). **3 operator decisions resolved per Recommended:** Q1 Maestro (pre-decided at autopilot kickoff); Q2 defer CI wiring (`15-4-followup-maestro-ci-wiring` filed); Q3 selector-verification TODO markers (vs hard-coded selectors — hardcoding without `maestro studio` verification is hope-driven). **Cross-story invariants preserved:** Story 12-8 password policy (test accounts use 10-char `Abcdefghi1`); Story 12-9 EmailVerificationGate (Flow 1 verifies it fires; Flows 2-5 assume verified accounts); Story 14-1 chrome rule (flows use English button/tab labels); Story 12-10 R1-H2 silent-disable defense (runbook explicitly forbids `continue-on-error: true` on the future CI step — partial gating is a footgun); 0 source-module modifications outside `.maestro/` + new drift test + runbook. **+5 net Jest cases** (drift only; Maestro flows execute outside Jest via `maestro test` CLI). All 5 design-system gates green. Verified 2026-05-17, story 15-4. + ### Routing (`app/`) Expo Router file-based routing with three route groups: diff --git a/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md b/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md new file mode 100644 index 0000000..51f3104 --- /dev/null +++ b/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md @@ -0,0 +1,89 @@ +# Story 15.4: Golden-flow E2E (Maestro skeleton flows + operator runbook) + +Status: review + +## Story + +As **a developer**, I want **5 Maestro YAML flow files documenting the golden user flows (sign-up / onboarding / first exercise / first conversation / partial mock test → review)** so that **an operator can execute end-to-end smoke tests locally + we have the structural framework for wiring Maestro into CI when simulator/emulator infrastructure is set up**. + +## Background + +[`shippable-roadmap.md`](_bmad-output/planning-artifacts/shippable-roadmap.md) line 296 — Epic 15.4 deliverable: "Golden-flow E2E with Detox or Maestro — sign-up → onboarding → 1 exercise → 1 conversation → mock test partial → review." + +**Operator pre-decision (autopilot batch kickoff):** **Maestro** chosen over Detox per the spec's recommended default — faster iteration, simpler YAML config, no Xcode/Android Studio project-level integration required. + +### Why SKELETON-ONLY scope + +Maestro requires a running iOS Simulator OR Android Emulator + a built app binary + Maestro CLI installed locally. None of these are available in the autopilot session. The cleanest scope-cut: ship the YAML flow files + operator runbook + drift detector; defer CI wiring to a follow-up after the operator sets up the simulator/emulator infrastructure on a dedicated runner. + +Maestro flow YAMLs are usable AS-IS by the operator once they install the CLI (`curl -Ls "https://get.maestro.mobile.dev" | bash`). + +## Acceptance Criteria + +### AC-A: 5 Maestro flow YAML files + +1. NEW `.maestro/config.yaml` declaring the app id (matches `app.json` bundleIdentifier / package name). +2. NEW `.maestro/01-signup-flow.yaml` — sign-up via email/password → email-verification screen. +3. NEW `.maestro/02-onboarding-flow.yaml` — 3-step onboarding wizard → placement test → home. +4. NEW `.maestro/03-first-exercise.yaml` — practice tab → grammar/vocabulary → complete 1 MCQ exercise. +5. NEW `.maestro/04-first-conversation.yaml` — conversation tab → topic select → start session → grant mic → speak → end. +6. NEW `.maestro/05-mock-test-partial-review.yaml` — mock-test tab → start full sim → complete listening section → submit (partial) → view results. + +Each flow file: +- Documents EXPECTED steps with `# TODO: verify selector` markers where the testID/accessibilityLabel needs operator verification against the actual app screens +- Uses `assertVisible`, `tapOn`, `inputText` Maestro commands + +### AC-B: Operator runbook + +7. NEW `_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md` documenting: + - Install Maestro CLI + - Set up iOS Simulator + Android Emulator + - Build a development client via `npx expo run:ios` / `npx expo run:android` + - Run flows locally: `maestro test .maestro/` + - Test account requirements + - Selector-verification workflow (`maestro studio` for interactive selector picking) + - Wire into CI procedure (deferred; documented for when operator infrastructure is ready) + +### AC-C: Drift detector + +8. NEW `src/lib/__tests__/maestro-flows-source-drift.test.ts` (≥4 cases) reading `.maestro/` dir: + - POSITIVE: 5 expected flow files exist with canonical names + - POSITIVE: each flow has a non-empty body + - POSITIVE: `.maestro/config.yaml` exists with the canonical appId + - NEGATIVE: no flow file is empty / placeholder-only (gap detection) + +### AC-D: Quality gates + cross-story + +9. All 5 design-system gates green. +10. **Net test growth:** **+4 to +6 net Jest cases** (drift detector only — Maestro flows execute outside Jest). +11. **No CI workflow changes** (deferred to `15-4-followup-maestro-ci-wiring`). +12. **0 source-module modifications.** + +## Operator Decisions + +| Q | Question | Recommended | +| --- | --- | --- | +| Q1 | Maestro vs Detox? | **Maestro** — pre-decided at autopilot kickoff | +| Q2 | Wire CI now or defer? | **Defer** — needs simulator/emulator infrastructure. File `15-4-followup-maestro-ci-wiring`. | +| Q3 | Inline selector hard-coding vs operator-verify TODO markers? | **TODO markers** — selectors must be verified against the actual app via `maestro studio`; hardcoding without verification is hope-driven | + +## Out of Scope + +- CI wiring (deferred) +- Actually-running the flows in this session (no simulator/emulator access) +- Selector verification (operator action; `maestro studio` workflow documented in runbook) + +## Tasks / Subtasks + +- [x] Task 1: 5 Maestro YAML flow files + `.maestro/config.yaml` +- [x] Task 2: Operator runbook +- [x] Task 3: Drift detector test +- [x] Task 4: Quality gates + CLAUDE.md paragraph + sprint-status + +## Dev Agent Record + +### Agent Model Used + +### Completion Notes List + +### File List diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 8875958..d523cca 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -35,7 +35,7 @@ # - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) generated: 2026-03-25 -last_updated: 2026-05-16 # Story 15-1 done. R1 review (5 patches: HIGH × 2 useFakeTimers time-flake fix + dictation empty-original semantic-trap TODO; MED × 3 Case 17 tightening + ref-inequality + typed-case-preservation Case 14a). 2159 tests (+1 net R1). All 5 gates green. PR #111. +last_updated: 2026-05-17 # Story 15-4 done (Maestro skeleton flows + runbook). Cumulative autopilot batch: 15-2 + 15-3 + 15-5 + 15-4 shipped. project: companion project_key: NOKEY tracking_system: file-system @@ -209,7 +209,7 @@ development_status: 15-1-lib-unit-tests: done # PR #111. R1 patches: HIGH × 2 (useFakeTimers time-flake fix; dictation empty-original semantic-trap TODO + 15-1-followup filed) + MED × 3 (Case 17 distinctive-marker assertion; Case 14 ref-inequality; new Case 14a typed-preserves-case). +1 net R1 (2158 → 2159). All 5 gates green. Original: 2026-05-16: Story 15-1 implementation complete. 4 NEW test files (srs.test.ts 14 cases + pronunciation.test.ts 10 cases + cache.test.ts 20 cases + use-dictation-compare.test.ts 15 cases) = +59 net Jest cases (2099 → 2158; squarely within spec target +50-65). 0 source-module modifications — test-only story. All 5 quality gates green. CLAUDE.md paragraph added per Epic 14 retro AI #5. Original: Story 15-1 spec file created. FIRST Epic 15 story. Pure-function lib unit tests for 4 modules where direct tests don't yet exist: srs.ts (SM-2 algorithm full coverage), pronunciation.ts identifyWeakSounds (pure aggregator), cache.ts core API (getCache/setCache/invalidateCache/cacheWithFallback happy + TTL boundary), use-dictation.ts compareSentences (pure word-comparison helper). Coverage inventory completed: activity.ts already fully tested (Story 9-2 + 12-3); memory.ts + error-tracker.ts have substantial partial coverage (sanitize via 9-4 + dedup via 11-6); pronunciation.ts has history-cap test (12-12); the GAP-only scope targets the 4 untested-or-partially-tested pure-function surfaces. Async/DB-touching paths (assessPronunciation Edge wrapper, memory.ts extractFacts/persistMemories/retrieveMemories, error-tracker.ts non-dedup paths) DEFERRED to 15-1-followups or 15-2 hook-integration scope per Story 14-4 R1-22-patch lesson (broad enforcement-rule changes interact with every surface; test-writing stories at analogous risk). 3 operator-decision items Q1-Q3 all resolved per Recommended (defer Edge wrapper / memory async / error-tracker non-dedup). 0 source-module modifications — test-only story. Spec target: +50-65 net Jest cases (2099 → 2149-2164). Status: ready-for-dev. Awaiting /bmad-dev-story. 15-2-hook-integration-tests: backlog 15-3-edge-function-deno-tests: backlog - 15-4-golden-flow-e2e: in-progress # 2026-05-17: spec written. Maestro chosen per operator pre-decision. SKELETON-ONLY scope — 5 YAML flow files + runbook + drift detector. CI wiring DEFERRED (needs simulator/emulator operator-setup). + 15-4-golden-flow-e2e: done # 2026-05-17: PR pending. SKELETON-ONLY scope shipped: .maestro/config.yaml + 5 YAML flow files + operator runbook + drift detector (5 cases). +5 net Jest cases. CI wiring DEFERRED to 15-4-followup-maestro-ci-wiring. 15-5-ai-schema-regression-tests: done # 2026-05-17: PR #114 pending. NEW fixture-replay infrastructure + 3 synthetic seed fixtures + operator runbook. +8 net Jest cases. Real fixture capture deferred to operator action per runbook. 15-6-ci-coverage-gating: backlog epic-15-retrospective: optional diff --git a/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md b/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md new file mode 100644 index 0000000..4318ca2 --- /dev/null +++ b/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md @@ -0,0 +1,166 @@ +# Maestro E2E Setup Runbook + +**Owner:** qa-engineer / operator +**Story:** 15-4 (Skeleton flows shipped 2026-05-17; CI wiring deferred to `15-4-followup-maestro-ci-wiring`) + +--- + +## What is this? + +Story 15-4 shipped 5 Maestro YAML flow skeletons covering the 5 golden user flows: +1. `01-signup-flow.yaml` — sign-up → EmailVerificationGate (Story 12-9) +2. `02-onboarding-flow.yaml` — sign-in → onboarding wizard → placement test → home +3. `03-first-exercise.yaml` — Practice tab → Grammar → AI exercise → grade +4. `04-first-conversation.yaml` — Conversation tab → topic → start session → end +5. `05-mock-test-partial-review.yaml` — Mock test → TCF full sim → submit partial → results + +**All flows carry `# TODO: verify selector` markers** because the testID / accessibilityLabel / text strings need to be confirmed against the actual running app via `maestro studio`. Until this verification pass is done, the flows are documentation, not executable. + +--- + +## Step 1 — Install Maestro CLI + +```bash +curl -Ls "https://get.maestro.mobile.dev" | bash +``` + +Verify: + +```bash +maestro --version +``` + +Expected: latest version (Maestro is updated frequently; pin via `maestro install latest` if needed). + +--- + +## Step 2 — Set up iOS Simulator + +Maestro requires Xcode + the iOS Simulator app. On a development machine: + +```bash +# Check available simulators +xcrun simctl list devices + +# Boot a simulator (e.g., iPhone 16 Pro running iOS 18) +xcrun simctl boot "iPhone 16 Pro" + +# Open Simulator.app +open -a Simulator +``` + +--- + +## Step 3 — Build a development client + +Maestro tests the BUILT app, not the Expo Go shell. Build a dev client: + +```bash +cd /Users/simplemart/Development/projects/personal/companion +npx expo run:ios --device "iPhone 16 Pro" +``` + +This installs `com.companion.app` on the booted simulator + starts Metro. + +For Android: + +```bash +npx expo run:android +``` + +--- + +## Step 4 — Verify selectors with `maestro studio` + +`maestro studio` launches an interactive UI where you can tap on the running app and Maestro extracts the testID / accessibilityLabel / text for each element: + +```bash +maestro studio +``` + +Walk through each of the 5 flows in `studio`. For each `# TODO: verify selector` marker in the YAML, replace with the actual selector studio shows. Common patterns: +- `tapOn: "Sign in"` works if the button has accessibilityLabel "Sign in" +- `tapOn: { id: "input-email" }` works if the TextInput has testID "input-email" +- If neither matches, add the testID to the React Native component source first + +--- + +## Step 5 — Seed test accounts + +Each flow uses an env-var-injected test email: +- `e2e-test+signup-001@invalid.localdomain` +- `e2e-test+onboarding-001@invalid.localdomain` +- `e2e-test+exercise-001@invalid.localdomain` +- `e2e-test+conversation-001@invalid.localdomain` +- `e2e-test+mocktest-001@invalid.localdomain` + +Flows 2-5 assume these accounts already exist + are email-verified. Seed via: + +**Option A (recommended):** Supabase Studio → Auth → Users → Add user (with verified flag). + +**Option B:** Run a one-off seed script: + +```ts +// scripts/seed-maestro-test-accounts.ts +import { createClient } from "@supabase/supabase-js"; +const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY); +for (const i of [1, 2, 3, 4, 5]) { + await supabase.auth.admin.createUser({ + email: `e2e-test+${flow}-00${i}@invalid.localdomain`, + password: "Abcdefghi1", + email_confirm: true, + }); +} +``` + +--- + +## Step 6 — Run flows locally + +```bash +# All flows +maestro test .maestro/ + +# Single flow +maestro test .maestro/03-first-exercise.yaml + +# By tag +maestro test --include-tags=smoke .maestro/ +``` + +Maestro outputs pass/fail per flow + writes a video of each run (helpful for debugging selector failures). + +--- + +## Step 7 — Wire into CI (deferred) + +When the operator is ready to wire CI, see `15-4-followup-maestro-ci-wiring`. CI setup requires: + +1. **Runner with simulator support** — macOS runner for iOS (paid GitHub Actions tier) OR an external service like BrowserStack / Maestro Cloud. +2. **Pre-built `.app` binary** — produced by `eas build --profile development --platform ios` and stored as a workflow artifact. +3. **Maestro CLI install step** — `curl -Ls https://get.maestro.mobile.dev | bash` in CI script. +4. **Test account env vars** — `MAESTRO_TEST_EMAIL_SIGNUP` / `_ONBOARDING` / `_EXERCISE` / `_CONVERSATION` / `_MOCKTEST` (+ `MAESTRO_TEST_PASSWORD`) as GitHub Actions secrets. +5. **Workflow step:** `maestro test .maestro/` + +Do NOT mark the CI step `continue-on-error: true` (Story 12-10 R1-H2 silent-disable defense). If E2E coverage isn't ready for CI gating, leave the step out of `ci.yml` entirely — partial gating is a footgun. + +--- + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| `maestro test` exits 1 with "element not found" | testID drift OR text drift after a UI refactor | Re-run `maestro studio`, update the YAML's selector | +| Permission dialog blocks Flow 4 | mic-permission dialog timing | Add a delay before the `tapOn: "OK"` for the system dialog | +| Flow 2 times out at placement test | AI generation > 120s | Increase `extendedWaitUntil.timeout` in the YAML; investigate prompt complexity | +| Flow 4 fails on Realtime connection | OpenAI Realtime API outage OR Edge Function rate-limit | Check Supabase logs for `realtime-session` errors | + +--- + +## Cross-story references + +- Story 12-8 password policy: test accounts must use ≥10-char passwords (`Abcdefghi1` complies) +- Story 12-9 EmailVerificationGate: Flow 1 verifies the gate fires; Flows 2-5 assume verified accounts +- Story 14-1 chrome rule: EN UI strings throughout — flows use English button/tab labels +- Story 13-4 parallel mock-test generation: Flow 5 leverages the listening-section-first availability +- Story 15-4-followup-maestro-ci-wiring: CI integration when operator infrastructure is ready From cee946306f612c181ac85c388b9ffc5df6136e56 Mon Sep 17 00:00:00 2001 From: Martins Aloba Date: Sun, 17 May 2026 15:04:52 -0500 Subject: [PATCH 3/3] =?UTF-8?q?fix(15-4):=20R1=20patches=20HIGH=20=C3=97?= =?UTF-8?q?=203=20+=20MED=20=C3=97=201=20+=20runbook=20(EH-1/EH-3/EH-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3-agent adversarial review surfaced 4 actionable Day-1 corrections plus 3 runbook gaps. SKELETON scope preserved. HIGH: - BH-1: Flow 1 asserted PRE-Story-14-1 French gate text. Per Story 14-1 R1-Q5, EmailVerificationGate was converted to English (verified against EmailVerificationGate.tsx:314+352). Corrected to "Verify your email address" + "I've verified — refresh". - BH-2: Tab labels in flows 4 + 5 did not match app/(tabs)/_layout.tsx 'title' values. The bottom-tab labels are "Talk" (line 61) and "TCF Test" (line 97), not "Conversation" / "Mock test" (those are headerTitle values, different surface). Corrected both flows. - BH-3: Flow 1 referenced 'input-password-confirm' but signup.tsx has ONE password field. Deleted the 3-line confirm block — delete-don't-alias pattern; operator runbook §5 documents the single-password contract. MED: - EH-4: Flow 4 mic-permission used iOS-only 'tapOn: "OK"' which fails on Android (Android dialog is "Allow" / "While using the app" / "Only this time"). Switched to Maestro's 'launchApp.permissions: { record_audio: allow }' directive which bypasses the system dialog entirely AND works cross-platform. Runbook (R1 EH-1 + EH-3 + EH-5): - Section 5 gains a Story 12-9 email-confirmation precheck: before running Flow 1, verify the Supabase project has the 'Confirm email' toggle ON via a curl-vs-jq one-liner. If OFF, Flow 1 dead-ends because the gate never renders. - Section 5 gains Flow 1 re-run cleanup guidance — 4 options ranging from manual Studio delete to per-run email suffix randomization via MAESTRO_RUN_ID env-var. - Section 5 hoists password to MAESTRO_TEST_PASSWORD env-var single source of truth — future Story 12-8 policy tightening updates one place (runbook + seed script). Deferred (filed as follow-ups): - 15-4-followup-add-testids (BH-4 — needs source edit) - 15-4-followup-yaml-vs-ts-constants-drift (EH-2 — drift detector cross-checking hardcoded times: 15/29 vs TCF constants) - 15-4-followup-appid-cross-check (EH-7 — config.yaml vs app.json) - 15-4-followup-mcq-correct-incorrect-alternation (EH-8 — RNG) Story file housekeeping: Status: review → done; populated Dev Agent Record per Acceptance Auditor APPROVE_WITH_NOTES. No net test count change (drift tests unchanged at 5/5). All 4 quality gates green: type-check 0 / lint 0 / prettier clean / jest 2164 tests pass. --- .maestro/01-signup-flow.yaml | 15 +++--- .maestro/04-first-conversation.yaml | 22 +++++--- .maestro/05-mock-test-partial-review.yaml | 6 ++- .../15-4-golden-flow-e2e.md | 25 ++++++++- .../runbooks/maestro-e2e-setup.md | 51 +++++++++++++++++-- 5 files changed, 97 insertions(+), 22 deletions(-) diff --git a/.maestro/01-signup-flow.yaml b/.maestro/01-signup-flow.yaml index 528ea9a..1f90e47 100644 --- a/.maestro/01-signup-flow.yaml +++ b/.maestro/01-signup-flow.yaml @@ -31,13 +31,16 @@ tags: id: "input-password" # TODO: verify testID - inputText: "Abcdefghi1" # min 10 chars per Story 12-8 password policy -- tapOn: - id: "input-password-confirm" # TODO: verify testID exists OR remove if single password -- inputText: "Abcdefghi1" +# R1 BH-3: signup.tsx has ONE password field (no confirm). Pre-R1 had a +# 3-line confirm block here — DELETED. Operator runbook §5 documents the +# single-password contract. # Submit - tapOn: "Create account" # TODO: verify exact button text -# Expect to land on EmailVerificationGate (Story 12-9) -- assertVisible: "Vérifiez votre adresse e-mail" # FR per Story 14-1 R1-Q5: gate stays EN per Q5 — verify current text -- assertVisible: "J'ai vérifié — actualiser" # TODO: verify the Refresh CTA label +# Expect to land on EmailVerificationGate (Story 12-9). R1 BH-1: Story +# 14-1 R1-Q5 converted the gate's chrome to English; pre-R1 these were +# the FR strings ("Vérifiez votre adresse e-mail" / "J'ai vérifié — +# actualiser") which would have failed Day 1. Corrected to EN. +- assertVisible: "Verify your email address" # EN per Story 14-1 R1-Q5 +- assertVisible: "I've verified — refresh" # TODO: verify exact CTA label against EmailVerificationGate.tsx diff --git a/.maestro/04-first-conversation.yaml b/.maestro/04-first-conversation.yaml index db7c9c5..4032adc 100644 --- a/.maestro/04-first-conversation.yaml +++ b/.maestro/04-first-conversation.yaml @@ -17,8 +17,15 @@ env: TEST_EMAIL: "e2e-test+conversation-001@invalid.localdomain" # TODO TEST_PASSWORD: "Abcdefghi1" --- +# R1 EH-4: Use Maestro's `launchApp.permissions` directive to grant +# RECORD_AUDIO upfront — bypasses the system permission dialog entirely +# AND works on both iOS Simulator + Android Emulator (eliminates the +# iOS-only "OK" / Android-only "Allow" / "While using the app" / +# "Only this time" alternation problem). - launchApp: clearState: false + permissions: + record_audio: allow - runFlow: when: @@ -32,8 +39,11 @@ env: - inputText: ${TEST_PASSWORD} - tapOn: "Sign in" -# Navigate to Conversation tab -- tapOn: "Conversation" # bottom tab — Story 14-1 R1 +# Navigate to Conversation tab. R1 BH-2: the tab `title` is "Talk" per +# `app/(tabs)/_layout.tsx:61`, NOT "Conversation". The `headerTitle` +# (rendered in the screen hero) is "Conversation" — but Maestro's +# `tapOn` on the bottom tab matches the `title` value. +- tapOn: "Talk" # bottom-tab title per _layout.tsx:61 - assertVisible: "Talk with Companion" # hero title from conversation/index.tsx # Pick the first topic (A1 — "Se présenter" greeting) @@ -45,12 +55,8 @@ env: - assertVisible: "Talk" # or whatever the start CTA reads — TODO - tapOn: "Talk" -# Grant mic permission (system dialog) -- runFlow: - when: - visible: "OK" # iOS permission dialog - commands: - - tapOn: "OK" +# R1 EH-4: permission dialog bypassed via launchApp.permissions above. +# The pre-R1 `runFlow when: visible: "OK"` block (iOS-only) is DELETED. # Wait for connection (status: "connected") - extendedWaitUntil: diff --git a/.maestro/05-mock-test-partial-review.yaml b/.maestro/05-mock-test-partial-review.yaml index 44e510b..4833abf 100644 --- a/.maestro/05-mock-test-partial-review.yaml +++ b/.maestro/05-mock-test-partial-review.yaml @@ -28,8 +28,10 @@ env: - inputText: ${TEST_PASSWORD} - tapOn: "Sign in" -# Navigate to Mock Test tab -- tapOn: "Mock test" # bottom tab — Story 14-1 R1 EN chrome +# Navigate to TCF Test tab. R1 BH-2: tab `title` is "TCF Test" per +# `app/(tabs)/_layout.tsx:97`, NOT "Mock test". The screen content +# refers to "Mock test" but the bottom-tab label is "TCF Test". +- tapOn: "TCF Test" # bottom-tab title per _layout.tsx:97 - assertVisible: "TCF" # mock-test/index.tsx hero brand # Tap the full TCF simulation card diff --git a/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md b/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md index 51f3104..0c80d27 100644 --- a/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md +++ b/_bmad-output/implementation-artifacts/15-4-golden-flow-e2e.md @@ -1,6 +1,6 @@ # Story 15.4: Golden-flow E2E (Maestro skeleton flows + operator runbook) -Status: review +Status: done ## Story @@ -84,6 +84,29 @@ Each flow file: ### Agent Model Used +Claude Sonnet 4.6 (claude-sonnet-4-6) via /bmad-dev-story + /bmad-code-review workflows in autopilot mode. + ### Completion Notes List +- **SKELETON-only scope shipped** per spec: `.maestro/config.yaml` + 5 YAML flow files (01 signup, 02 onboarding, 03 first-exercise, 04 first-conversation, 05 mock-test partial review) + 7-section operator runbook + drift detector. Maestro CLI wiring into CI deferred to `15-4-followup-maestro-ci-wiring`. +- **R1 patches applied** (HIGH × 3 + MED × 1 + runbook updates): BH-1 Flow 1 EmailVerificationGate strings converted FR → EN per Story 14-1 R1-Q5 (`"Verify your email address"` + `"I've verified — refresh"`); BH-2 tab labels in flows 4 + 5 corrected to match `app/(tabs)/_layout.tsx` `title` values (`"Talk"` not `"Conversation"`; `"TCF Test"` not `"Mock test"`); BH-3 deleted nonexistent `input-password-confirm` field in Flow 1 (signup.tsx has ONE password field); EH-4 Flow 4 mic-permission grant switched from iOS-only `tapOn: "OK"` to `launchApp.permissions: { record_audio: allow }` for cross-platform parity. Runbook §5 gains R1 EH-1 Flow 1 re-run cleanup guidance (4 options including per-run email suffix randomization), R1 EH-3 Supabase email-confirmation precheck (verify Story 12-9 toggle ON before Flow 1), and R1 EH-5 password single-source-of-truth via `MAESTRO_TEST_PASSWORD` env var. +- **Deferred** (filed as follow-ups): `15-4-followup-add-testids` (BH-4 — testID props missing from auth screens; needs source edit); `15-4-followup-yaml-vs-ts-constants-drift` (EH-2 — drift detector cross-checking hardcoded `times: 15` / `times: 29` vs `src/lib/constants.ts` TCF constants); `15-4-followup-appid-cross-check` (EH-7 — `.maestro/config.yaml` appId vs `app.json` bundle identifiers); `15-4-followup-mcq-correct-incorrect-alternation` (EH-8 — Flow 3 binary correct/incorrect path doesn't handle RNG-positioned correct answers). `15-4-followup-maestro-ci-wiring` (original spec deferral) stands. +- **Quality gates green**: type-check 0 errors / lint 0 warnings / prettier clean / drift detector 5/5 pass / no Jest test count change (drift tests untouched at +5). + ### File List + +**New:** + +- `.maestro/config.yaml` (appId: com.companion.app) +- `.maestro/01-signup-flow.yaml` (round-1: EN gate strings; password-confirm field deleted) +- `.maestro/02-onboarding-flow.yaml` +- `.maestro/03-first-exercise.yaml` +- `.maestro/04-first-conversation.yaml` (round-1: "Talk" tab label; `launchApp.permissions` instead of iOS-only `tapOn: "OK"`) +- `.maestro/05-mock-test-partial-review.yaml` (round-1: "TCF Test" tab label) +- `src/lib/__tests__/maestro-flows-source-drift.test.ts` — 5 drift cases +- `_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md` (round-1: §5 Flow 1 re-run cleanup + email-confirmation precheck + password SSOT) + +**Modified:** + +- `_bmad-output/implementation-artifacts/sprint-status.yaml` — 15-4 → done +- `CLAUDE.md` — Story 15-4 architecture paragraph appended diff --git a/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md b/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md index 4318ca2..30c8817 100644 --- a/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md +++ b/_bmad-output/planning-artifacts/runbooks/maestro-e2e-setup.md @@ -87,14 +87,53 @@ Walk through each of the 5 flows in `studio`. For each `# TODO: verify selector` ## Step 5 — Seed test accounts +### Prerequisites — verify Story 12-9 email-verification gate is ON + +**R1 EH-3 precheck:** Flow 1 asserts the EmailVerificationGate appears after signup. This requires the Supabase project to have **Authentication → Providers → Email → "Confirm email" toggled ON** (per the Story 12-9 runbook). If your test environment was created with the toggle OFF (or you followed the 12-9 rollback procedure), Flow 1 will dead-end because the new user is routed straight to onboarding — the gate never renders. + +Verify with: + +```bash +# Quick precheck — signup against the project; response body should +# carry `email_confirmed_at: null` (gate active) AND no session token +# (gate is gating session establishment). +curl -X POST "$SUPABASE_URL/auth/v1/signup" \ + -H "apikey: $SUPABASE_ANON_KEY" \ + -H "Content-Type: application/json" \ + -d '{"email":"precheck-12-9@invalid.localdomain","password":"Abcdefghi1"}' | jq '.user.email_confirmed_at, .session' +``` + +If `email_confirmed_at` is not null OR a `session` object is present, the gate is OFF — toggle it ON in the Supabase Dashboard before running Flow 1. + +### Test accounts + Each flow uses an env-var-injected test email: -- `e2e-test+signup-001@invalid.localdomain` + +- `e2e-test+signup-001@invalid.localdomain` (Flow 1 only — created at runtime) - `e2e-test+onboarding-001@invalid.localdomain` - `e2e-test+exercise-001@invalid.localdomain` - `e2e-test+conversation-001@invalid.localdomain` - `e2e-test+mocktest-001@invalid.localdomain` -Flows 2-5 assume these accounts already exist + are email-verified. Seed via: +**Flows 2–5 assume these accounts already exist + are email-verified + have completed onboarding.** The Flow 1 account is created fresh on each run (which means re-running Flow 1 against the same Supabase project requires per-run cleanup — see "Flow 1 re-run cleanup" below). + +**Password single source of truth (R1 EH-5):** All flows reference `${TEST_PASSWORD}` via Maestro env-var. The seed script + flow files all consume the same `MAESTRO_TEST_PASSWORD` env var when set; the default value `Abcdefghi1` is hardcoded for convenience. If Story 12-8's `MIN_PASSWORD_LENGTH` tightens above 10 in the future, update the env var default in this runbook + the seed script — the flow files inherit via `${TEST_PASSWORD}`. + +```bash +export MAESTRO_TEST_PASSWORD="Abcdefghi1" # must satisfy Story 12-8 policy +maestro test --env TEST_PASSWORD=$MAESTRO_TEST_PASSWORD .maestro/ +``` + +### Flow 1 re-run cleanup (R1 EH-1) + +Flow 1 creates the `e2e-test+signup-001@invalid.localdomain` account fresh. **A second invocation against the same Supabase project fails on "user already exists"** because the email is unique-indexed. Pick one of: + +1. **Delete the account between runs** (manual): Supabase Studio → Auth → Users → search `signup-001` → Delete. +2. **Delete the account between runs** (scripted): `supabase auth users delete ` via the CLI. +3. **Randomize the email suffix** (cleanest): edit Flow 1 to use `e2e-test+signup-${MAESTRO_RUN_ID}@invalid.localdomain` and pass `--env MAESTRO_RUN_ID=$(date +%s)` to `maestro test`. The signup gate accepts any unique email; the test asserts the gate state, not a specific email. +4. **Skip Flow 1 on subsequent runs**: `maestro test --include-tags=smoke --exclude-tags=signup .maestro/`. + +### Seed Flows 2–5 (one-time setup) **Option A (recommended):** Supabase Studio → Auth → Users → Add user (with verified flag). @@ -104,10 +143,12 @@ Flows 2-5 assume these accounts already exist + are email-verified. Seed via: // scripts/seed-maestro-test-accounts.ts import { createClient } from "@supabase/supabase-js"; const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY); -for (const i of [1, 2, 3, 4, 5]) { +const password = process.env.MAESTRO_TEST_PASSWORD ?? "Abcdefghi1"; +const flows = ["onboarding", "exercise", "conversation", "mocktest"]; +for (const flow of flows) { await supabase.auth.admin.createUser({ - email: `e2e-test+${flow}-00${i}@invalid.localdomain`, - password: "Abcdefghi1", + email: `e2e-test+${flow}-001@invalid.localdomain`, + password, email_confirm: true, }); }