test(e2e): full user-journey spec against production (landing, scan, auth, browse)#76
Conversation
…o collage
User report: "I uploaded 2 photos, but got results from only 1."
Root cause: when the client uploads N>1 photos, the scan hook stitches
them into a single grid image via stitchImagesToCanvas(), then POSTs
that image to /api/analyze. The server forwards it to Gemini with the
stock meal/menu/drink_snack prompt, which has no language describing
the collage layout. The model frequently interprets the stitched image
as a single scene and returns one dish in `dishes[]` (meal mode) or
ignores all but one tile (menu mode), so the UI can only display one
result.
Fix:
- `useScanAnalysis.ts`: include `photoCount: uploadedImages.length` in
the POST body.
- `analyze/route.ts`: parse the photoCount, clamp to [1, 12] (matches
the stitcher's 4x3 max grid), log it in REQUEST_RECEIVED, and thread
it through both inference paths (`buildLocalizedPrompt(..., photoCount)`).
- `ai-prompt.ts`: `buildLocalizedPrompt` now takes an optional photoCount.
When > 1, a `buildCollageInstruction()` preamble is prepended that:
- Tells the model the image is a grid of N SEPARATE photos on a white
background with white padding between tiles.
- For `meal`: requires one `dishes[]` entry per tile, sets dishCount=N,
in reading order (left-to-right, top-to-bottom).
- For `menu`: collects items from ALL N tiles into a single menuItems[]
array (the user often photographs multi-page menus).
- For `drink_snack`: schema is single-item, so the model is told to
pick the most prominent tile and deterministically analyze it.
Single-photo scans are unaffected — the builder signature defaults
photoCount=1 and skips the preamble.
Verified: `npm run build` compiles cleanly.
https://claude.ai/code/session_01BWLU1nKZpxToH5hMeBDusG
Previously the history entry for a meal-mode scan took only dishes[0]'s
name, nutrition, and spikeReduction — so a 2-dish meal appeared in
history as "Chicken Curry" with the nutrition of just the first dish,
and the second dish was lost.
This commit:
- Extends `ScanHistoryEntry` with an optional `dishes: ScanHistoryDish[]`
field. Each dish carries name + spikeReduction + nutrition (the fields
a list view would render). Heavier per-dish data (score breakdowns,
eating-sequence steps) is intentionally omitted to keep localStorage
usage small — the entry cap is 10, so adding the array keeps total
size bounded.
- Adds `summarizeMealForHistory()` in useScanAnalysis that:
* Sums nutrition across all dishes so the top-level `nutrition` field
reflects the whole plate, not just dish #1.
* Averages spikeReduction across dishes (matches MealOverview's header).
* Labels the summary "Pad Thai + 2 more" for multi-dish meals so the
user can distinguish a multi-photo scan from a single-dish scan.
- Leaves the top-level fields (`foodName`, `nutrition`, `spikeReduction`)
populated so any existing or future read-only UI still works without
traversing the `dishes` array.
- `dishes` is optional, so entries written before this change remain
valid (backward-compatible schema).
Menu and drink_snack modes are unchanged — their server-side schemas
are already single-record-per-scan.
Verified: tsc --noEmit and `npm run build` both pass.
https://claude.ai/code/session_01BWLU1nKZpxToH5hMeBDusG
Drives the production app (https://shinnyguide.autobahn.bot) end-to-end the way a real user would, in four independent phases: 1. Landing + locale switch — /th renders, switching to /en works, hreflang graph covers all 4 locales + x-default. 2. Scan flow — uploads a 256x256 noisy PNG (generated in-memory so the spec stays self-contained — no binary fixture), clicks the "Analyze Now" CTA, waits for the analyze cascade (Workers AI -> Gemini) to land on a terminal UI state. The PNG is incompressible enough to clear the 500-byte client-side floor in useScanUpload.ts and noisy enough that Gemini routes into the not_food branch — valid terminal state for the "did the journey complete" question. 3. Auth round-trip — POSTs /api/auth/register with a fresh unique email and no voucher. Verifies the form settles on either an inline error banner (voucher_required, the expected path with VOUCHER_REQUIRED_FOR_REGISTRATION on) OR a success card, and explicitly fails if the Next.js client-side error boundary engages — that was caught in the May/Jun 2026 bug-hunt round. 4. Recipes / chat / dashboard — visits each and verifies a stable rendered state (own UI or auth-redirect) without fatal console errors. Filters out known-benign noise: /api/auth/me 401 (fixed in PR #56), Next.js RSC prefetch fallback. Runtime against warm production: ~10s for all 4 phases. The scan phase has its own 180s timeout to absorb cold-start Workers AI + Gemini fallback cascade. Run: cd frontend && npx playwright test tests/e2e/user-journey.spec.ts
Test-results/ + playwright-report/ are local-run artifacts (HTML reports, trace files, screenshots on failure). Adding them now so the new tests/e2e/user-journey.spec.ts run doesn't leave dirty working-tree noise behind.
…ranch The user-journey spec landed in 5d9d51d, but this branch predates the Playwright layer (config + devDep + scripts lived only on later branches). Port playwright.config.ts as-is and add @playwright/test + test:e2e scripts so 'npm run test:e2e' works here. Verified: all 4 journey phases pass against production from this branch (40s, scan phase exercised the cold-path timeout terminal state and the UI handled it).
…factor-212i2 # Conflicts: # frontend/package.json # frontend/src/app/api/analyze/route.ts # frontend/src/hooks/scan/useScanAnalysis.ts # frontend/src/lib/ai-prompt.ts
|
Warning Review limit reached
More reviews will be available in 21 minutes and 13 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Round 12 added tests/e2e/user-journey.spec.ts (PRs #76-#78) — the fifth testing lens: one spec walking production end-to-end through the rendered UI. This commit records it everywhere the project documents its testing posture, approach, and known issues: - README / README-TH: e2e count 93 → 97 (6 spec files), four-lens → five-lens posture, Round 12 summary line, quality-gate table. - CHANGELOG: full Round 12 entry — phase table, what building the spec itself surfaced (intermittent register error-boundary crash, silent sub-500-byte file rejection, duplicate-label button trap, benign console-noise classes), side-effect budget, test posture. - RELEASE_NOTES: Round 12 section at top (unreleased). - CONTRIBUTING: journey-spec section in Testing with run command, per-run cost, and the four extension rules. - docs/GUIDELINE: "The full user-journey lens" — the approach (terminal- state assertions, runtime fixtures above client floors, type=submit over text selectors, documented-pattern console filtering) and method (phase independence, crash sentinels, side-effect budgets, when to run). Coverage table updated to 171 unit / 97 e2e. - docs/ITERATION_PROCESS: §3 notes the spec automates manual checks 1/3/4/5; §9 four-lens → five-lens table with the Round 12 row and the integration-seam lesson. - docs/KNOWLEDGE_BASE: user-journey row in the Playwright table + a Round 12 section with the seven design decisions for extending it. - docs/KNOWN_ISSUES: new §2a (intermittent client error boundary on register response — sentinel-pinned, medium priority) and §5 (known-benign console-error noise, documented so new specs reuse the shared filter instead of rediscovering "flakes"). - docs/claude.md + docs/gemini.md: AI-guide sections pointing at the spec, its traps, and the GUIDELINE recipe.
Summary
Adds
frontend/tests/e2e/user-journey.spec.ts— a browser-driven full user journey against production (https://shinnyguide.autobahn.bot), exercising the app the way a real user would in four independent phases:/threnders, switching to/enre-hydrates under the new lang, hreflang graph covers all 4 locales +x-default, zero console errors.useScanUpload.ts), clicks the "Analyze Now" CTA, and waits for the analyze cascade (Workers AI → Gemini) to land on a terminal UI state — meal result, not-food card, or handled-error card. Own 180s timeout absorbs cold-start cascades./api/auth/registercontract (voucher_requiredon 400) AND that the form settles on an inline error/success — explicitly failing if the Next.js client error boundary engages (a crash observed during test development).Also brings the branch up to date with
main(merge commitd428ff8); both scan commits unique to this branch had already landed on main via #8-era PRs, so conflicted source files were resolved by taking main's newer versions.Verification
npm run check:all— type-check + i18n key check + 171/171 unit tests passnpx playwright test tests/e2e/user-journey.spec.ts— 4/4 phases pass against production, ~17s warm (repeated runs stable, including one run that exercised the cold-path analyze timeout and confirmed the UI's handled-error terminal state)Side-effect budget per run
not_foodbranch)https://claude.ai/code/session_01BWLU1nKZpxToH5hMeBDusG
Generated by Claude Code