═══════════════════════════════════════════════════════════════════════════════ MANUAL TESTING GUIDE — Tour Planner Playground (tour-pg-v1) PWA: https://srv1111289.hstgr.cloud/tour-pg/ Proxy: https://srv1111289.hstgr.cloud/tour-pg-proxy/ Sample: https://srv1111289.hstgr.cloud/tour-pg/sample.xlsx Default access: harish, pramod, rakesh (granted via tour-pg access_group) ═══════════════════════════════════════════════════════════════════════════════ Sections A. Pre-flight B. Auth + session C. Excel parsing + column detection D. Geocoding (origin + stops) E. Optimise routing (≤12 stops — TomTom server) F. Optimise routing (>12 stops — matrix + 2-opt) G. Tile editor — drag, include/exclude H. Mid-tour replan I. Share / export / save / restore J. Negative + edge cases K. Tear-down ╔══════════════════════════════════════════════════════════════════════════════╗ ║ A. PRE-FLIGHT ║ ╚══════════════════════════════════════════════════════════════════════════════╝ A1. Verify the four services are up: $ docker ps | grep -E 'mcp-google-maps|mcp-tomtom-maps|tour-pg-proxy' Expect three containers, all "Up". A2. Verify the PWA + proxy reach: curl https://srv1111289.hstgr.cloud/tour-pg-proxy/health → {"ok":true,"service":"tour-pg-proxy"} curl -o /dev/null -w "%{http_code}\n" https://srv1111289.hstgr.cloud/tour-pg/sample.xlsx → 200 A3. Verify Hub registration: docker exec postgres psql -U lmadmin -d lm360 -c \ "SELECT id, url, is_live FROM hub.pwa_registry WHERE id='tour-pg';" A4. Log in to /hub/ as harish (PIN). Confirm the Tour Playground tile is visible (under your QA tiles since qa_eligible=TRUE). ╔══════════════════════════════════════════════════════════════════════════════╗ ║ B. AUTH + SESSION ║ ╚══════════════════════════════════════════════════════════════════════════════╝ B1. NO SESSION DevTools → Application → Local Storage → delete `lm360-session`. Reload /tour-pg/. ✅ Auto-redirects to /hub/. B2. EXPIRED SESSION DevTools → Application → Local Storage → edit `lm360-session.loginAt` to 13 hours ago. Reload. ✅ Redirects to /hub/. B3. VALID SESSION Log back in via /hub/. Return to /tour-pg/. ✅ Upload screen renders. User pill shows your name. B4. PROXY 401 MID-EDIT Log in. Upload Excel. Optimise. After landing on editor, in DevTools overwrite `lm360-session.loginAt` to 13h ago. Drag a tile. ✅ The recalc fires → 401 from proxy → toast "Session expired" → redirect to /hub/ after ~1.5s. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ C. EXCEL PARSING + COLUMN DETECTION ║ ╚══════════════════════════════════════════════════════════════════════════════╝ C1. SAMPLE FILE (12 stops, pre-geocoded) Upload sample.xlsx. ✅ Toast "Excel parsed ✓". ✅ "12 stops detected · cols: name, city, address, lat, lng, phone, notes" ✅ "Parse + optimise route" button enabled (sky-blue). C2. CUSTOM EXCEL — MINIMAL Create a 3-row Excel: | name | address | | Test Store 1 | Sector 17 Plaza Chandigarh | | Test Store 2 | Phase 7 Mohali | | Test Store 3 | Panchkula Sector 5 | Upload. ✅ "3 stops detected · cols: name, address" C3. CUSTOM EXCEL — ALIAS COLUMNS Test that column-name aliases work. Header row should be all detected: "store" → name "client" → name "outlet" → name "location" → address "loc" → address "town" → city "district" → city "latitude" → lat "longitude" → lng "mobile" → phone "contact" → phone C4. CUSTOM EXCEL — INVALID ROWS Mix valid and invalid rows (some with empty name AND address). ✅ Skip count surfaces: "5 stops detected · 2 skipped (missing name/address)" C5. CSV INSTEAD OF XLSX Save sample as CSV. Upload. ✅ SheetJS parses transparently. Same result. C6. EMPTY SHEET Upload an Excel with header row but no data rows. ✅ Toast "Excel error: Empty sheet" ╔══════════════════════════════════════════════════════════════════════════════╗ ║ D. GEOCODING ║ ╚══════════════════════════════════════════════════════════════════════════════╝ D1. PRE-GEOCODED STOPS (sample.xlsx has lat/lng) Upload sample → tap optimise. ✅ Progress bar shows "Pre-geocoded 12 / 12" almost instantly. ✅ Origin geocoded once (1 API call total for the whole tour). D2. STOPS WITHOUT COORDS Create a 3-row Excel WITHOUT lat/lng columns. Upload → optimise. ✅ Progress bar updates "Geocoding Test Store 1…", "… 2…", "… 3…". ✅ Total time ~1 second per stop (80ms sleep between, plus API latency). D3. ORIGIN GEOCODE FAIL Change origin to "QQ ZZ XX UNKNOWN ADDRESS 99999". Try optimise. ✅ Toast "Origin geocode failed: …" — does NOT proceed. ✅ Button re-enabled. D4. STOP GEOCODE FAIL (partial) Create an Excel with one valid address and one nonsense address. Upload → optimise. ✅ Progress bar runs to completion. ✅ Toast warning "1 stop(s) failed geocode (excluded)" — yellow/red. ✅ Editor opens with the valid stops only. D5. PROVIDER ROUTING (audit) DevTools Network tab open. Upload non-pre-geocoded Excel. Optimise. Filter Network for "geocode" calls. ✅ Each /geocode response body has `"provider":"google"` (Google is primary per Maps Provider Policy). If Google quota runs out you should see `"provider":"tomtom"` as fallback. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ E. OPTIMISE — ≤12 stops (TomTom server-side) ║ ╚══════════════════════════════════════════════════════════════════════════════╝ E1. With sample.xlsx (12 stops): ✅ Sub-header shows "12 of 12 included · tomtom-computeBestOrder" ✅ POST /tour-pg-proxy/optimize response carries `"optimisation": "tomtom-computeBestOrder"` E2. SMALLER SET — 4 stops Use a custom 4-stop Excel. ✅ Optimise call uses computeBestOrder; response has 4-element waypoint_order permutation. E3. EXACT 12 STOPS (sample as-is) ✅ Still routed to computeBestOrder. Boundary OK. E4. SINGLE STOP 1-stop Excel. ✅ Optimise call succeeds; waypoint_order = [0]. Route is just origin → stop → origin (3 legs for round-trip with 1 stop). ╔══════════════════════════════════════════════════════════════════════════════╗ ║ F. OPTIMISE — 13–30 stops (matrix + 2-opt heuristic) ║ ╚══════════════════════════════════════════════════════════════════════════════╝ F1. 14-STOP EXCEL Create / upload a 14-stop Excel (pre-geocoded for speed). ✅ Sub-header: "14 of 14 included · matrix-nn-2opt" ✅ Proxy response: `"optimisation": "matrix-nn-2opt"` ✅ Order is a valid permutation of 0..13 (no duplicates). ✅ Latency higher than ≤12 case (the proxy does 2+ matrix chunks). F2. 30-STOP CEILING Upload a 30-stop Excel. ✅ Works. F3. 31+ STOPS REJECT Upload a 31-stop Excel. ✅ Toast "Optimise failed: max 30 stops (UX cap)". ╔══════════════════════════════════════════════════════════════════════════════╗ ║ G. TILE EDITOR ║ ╚══════════════════════════════════════════════════════════════════════════════╝ G1. GENERAL LAYOUT ✅ Tiles render in optimised order. ✅ Each shows: drag grip, ord badge, name, city, leg metric (from prev), provider badge, include toggle. ✅ Tile 1's leg metric is "from Mohali office" (the origin). G2. DRAG REORDER Drag tile 5 to position 2. ✅ Animation. ✅ Numbers renumber. ✅ Recalc fires after 400ms (↻ briefly appears next to share button). ✅ Totals update. Delta colours: green if shorter, yellow if longer. G3. TOUCH DRAG (phone / tablet) Test on a real touch device. ✅ Long-press grip → drag → release. ✅ Touch scrolling does not interfere with drag. G4. EXCLUDE Tap the ✓ on tile 3. ✅ Turns red ✗. Tile dims; strikethrough name/city. Leg metric hidden. ✅ Recalc. G5. INCLUDE BACK Tap the ✗ on the excluded tile. ✅ Restores fully. G6. EXCLUDE ALL Toggle ✗ on every tile. ✅ Totals bar shows "0.0 km · 0 min" or similar. G7. RE-OPTIMISE BUTTON After excluding 3 tiles, tap "🔁 Re-optimise". ✅ Toast "Optimising…" then "Re-optimised ✓". ✅ Included tiles re-order; excluded tiles drop to the tail. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ H. MID-TOUR REPLAN ║ ╚══════════════════════════════════════════════════════════════════════════════╝ H1. SIMULATED OVERNIGHT HALT After optimising sample.xlsx, toggle ✗ on the first 3 stops in order (these are the "completed" ones). Tap ✏️ Change on the origin pill. Enter "Panchkula Sector 5 Haryana". Tap "🔄 Replan from here". ✅ Origin pill shows new value. ✅ Remaining 9 tiles reorder from Panchkula. ✅ Total distance recalculated. H2. INVALID NEW ORIGIN ✏️ Change → enter "UNKNOWN PLACE" → Replan. ✅ Toast "Origin failed: …" — original origin kept. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ I. SHARE / EXPORT / PERSISTENCE ║ ╚══════════════════════════════════════════════════════════════════════════════╝ I1. SHARE MODAL CONTENT Tap 📤 Share. ✅ Modal opens with formatted text: - "🚗 Tour plan (date)" - From: - "N stops · X km · Y min" - Round-trip or "One-way → …" - Numbered stop list with leg deltas and phones if present - Return-to-origin line if round-trip ✅ Excluded stops do NOT appear in the share text. I2. COPY TEXT Tap 📋 Copy text. ✅ Toast. Paste into Notes → exact match. I3. CSV DOWNLOAD Tap 📥. ✅ File "tour-YYYY-MM-DD.csv" downloads. Open in Excel/Sheets. ✅ One row per included stop with columns: #, Name, City, Address, Phone, Lat, Lng, Leg km, Leg min, Provider. I4. SAVE TO DEVICE Tap 💾. ✅ Toast "Saved ✓". DevTools → Application → Local Storage → key `tour-pg-saved` present with the state JSON. I5. WHATSAPP Tap 💬. ✅ wa.me URL opens. On mobile this should launch WhatsApp. I6. LOAD AFTER RELOAD Reload page. ✅ Upload screen. ✅ "↻ Load" button visible in header. Tap. ✅ Editor restored. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ J. NEGATIVE + EDGE CASES ║ ╚══════════════════════════════════════════════════════════════════════════════╝ J1. CLICK OPTIMISE BEFORE UPLOAD → button disabled, no-op. J2. UPLOAD EXCEL WITH HEADER-ONLY ROW → toast "Excel error: Empty sheet". J3. SHARE WITH 0 INCLUDED STOPS → modal shows "0 stops · 0 km". ✅ Acceptable; user can re-include. J4. SLOW NETWORK (DevTools throttle to "Slow 3G") → recalc takes 5–10s. ✅ The ↻ indicator stays on the whole time. J5. POP-UP BLOCKER ACTIVE → WhatsApp share opens fine (it's a direct navigation, not popup). CSV download fine. J6. PROXY DOWN (`docker stop tour-pg-proxy`) → optimise fails with a 502 / network error toast. PWA does not crash. J7. MCP DOWN (`docker stop mcp-tomtom-maps`) → proxy returns 502. Same behavior. Restart MCP, retry — works. J8. EXCEL WITH > 30 STOPS → proxy returns 400 "max 30 stops". ╔══════════════════════════════════════════════════════════════════════════════╗ ║ K. TEAR-DOWN ║ ╚══════════════════════════════════════════════════════════════════════════════╝ K1. CLEAR LOCAL STORAGE DevTools → Application → Local Storage → delete `tour-pg-saved`. K2. REVOKE ACCESS (if testing with a non-default user) docker exec postgres psql -U lmadmin -d lm360 -c \ "DELETE FROM hub.employee_pwa_access WHERE access_group='tour-pg' AND employee_id='';" K3. NO PRODUCTION DATA TOUCHED The PWA writes nothing to any DB. Nothing to clean up beyond localStorage on the testing device. ═══════════════════════════════════════════════════════════════════════════════ TEST PASS CRITERIA - All sections A–J behave as described. - 14-stop tour completes in <30 seconds end-to-end. - Drag reorder latency under 1 second on a desktop / 2 seconds on a mid-range phone. - CSV opens cleanly in Excel + Google Sheets. - No production-side DB writes. ═══════════════════════════════════════════════════════════════════════════════