Files
intotheeast-com/docs/working/qa/test-plan.md
T
m038 fac3b18201 docs: fix TC-M QA cases — correct URLs, hover rule, highlight detail
- Wrong active trip slug in intro (japan-korea → us-canada-mex-2024)
- Home page URL /home not /
- TC-M.3: describe teal-fade animation precisely, not "background flash"
- TC-M.6: arrows hidden by hover:none (touch devices), not by screen width
- TC-M.7: add note that home feed only renders in trip mode; explain how to verify

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 22:05:41 +02:00

28 KiB
Raw Blame History

QA Test Plan

Branch: experimental-polar-steps. Tester role: Senior Staff QA Engineer.


Scope

All features implemented in Phase 4 (Milestones 14):

  • M1: Entry enrichment (location badge, weather badge, photo gallery, hero image)
  • M2: Interactive map page
  • M3: Statistics page
  • M4: Mini-map on tracker feed

Test URLs:


Milestone 1 — Entry Enrichment

TC-1.1: Location badge on entry page

Step Action Expected Result
1 Open http://localhost:8081/tracker/2026-06-17.entry Entry page loads (200)
2 Look at entry header 📍 Amsterdam, Netherlands visible below date
3 Inspect HTML <p class="entry-location"> present with city and country

Automation: grep for .entry-location and "Amsterdam" in curl output


TC-1.2: Weather badge on entry page

Step Action Expected Result
1 Open http://localhost:8081/tracker/2026-06-17.entry Entry page loads
2 Look at entry header ⛅ Partly cloudy · 19°C visible
3 Inspect HTML <p class="entry-weather"> present

Automation: grep for .entry-weather and "Partly cloudy" and "19°C"


TC-1.3: Location badge hidden when fields empty

Step Action Expected Result
1 Create test entry with no location_city/location_country
2 Open that entry No 📍 badge shown, no empty <p> rendered

Automation: Check example entry before fields were added (not needed — fields are now set); create a second test entry without location


TC-1.4: Weather badge hidden when fields empty

Step Action Expected Result
1 Entry with no weather fields No weather section in HTML

Automation: grep for entry-weather in HTML — should only appear if value present


TC-1.5: Hero image on tracker feed card

Step Action Expected Result
1 Open http://localhost:8081/tracker Feed loads
2 Entry card for 2026-06-17 No image shown (example entry has no photos)
3 Upload a photo to the entry via Admin media manager
4 Reload tracker Hero image shows as 16:9 thumbnail

Manual verification required: Photo upload requires browser Admin interaction


TC-1.6: Location badge on tracker feed card

Step Action Expected Result
1 Open http://localhost:8081/tracker Feed loads
2 Entry card 📍 Amsterdam, Netherlands visible

Automation: grep feed HTML for entry-location--card and "Amsterdam"


TC-1.7: Photo gallery renders on entry page (with photos)

Step Action Expected Result
1 Upload 3 photos to the example entry via Admin
2 Open entry page Gallery grid appears below entry body
3 Count thumbnails 3 thumbnails in 2-col (mobile) / 3-col (desktop) grid
4 Click a thumbnail Lightbox overlay opens with full-size image
5 Press Escape Lightbox closes
6 Click left/right arrow buttons Navigates between images
7 Click outside lightbox Lightbox closes

Manual verification required: Photo upload and interactive lightbox require browser


TC-1.8: Post form has location and weather fields

Step Action Expected Result
1 Open http://localhost:8081/post (logged in) Post form renders
2 Inspect form City and Country text inputs present
3 Inspect form 📍 Get Current Location and 🌤 Get Weather buttons present

Automation: grep /post HTML for location_city, location_country, get-location, get-weather


TC-1.9: Get Weather button fills fields

Step Action Expected Result
1 Open /post on phone Post form loads
2 Tap "Get Current Location" Lat/lng fields fill with coordinates
3 Tap "Get Weather" Status shows "🌤 Weather set: [desc] · [temp]°C"
4 Submit form New entry created with weather in frontmatter
5 Open entry in Admin weather_temp_c and weather_desc fields populated

Manual verification required: Geolocation and form submission require mobile browser


Milestone 2 — Interactive Map

TC-2.1: Map page loads

Step Action Expected Result
1 GET http://localhost:8081/map HTTP 200
2 Inspect HTML <div id="trip-map"> present
3 Inspect HTML Leaflet CSS and JS from CDN present

Automation: curl + HTTP status check; grep for "trip-map" and "leaflet"


TC-2.2: Entry with GPS appears in ENTRIES JSON

Step Action Expected Result
1 curl http://localhost:8081/map Map page HTML
2 grep for var ENTRIES Array contains Amsterdam entry with lat 52.3676
3 Check entry has title, date, url All fields present

Automation: grep output for ENTRIES and lat value


TC-2.3: Map renders marker and route in browser

Step Action Expected Result
1 Open /map in browser Map tiles load, marker visible
2 Click marker Popup opens with "The Journey Begins" title and "Read entry →" link
3 Click "Read entry →" Navigates to entry page

Manual verification required: Leaflet rendering requires browser


Step Action Expected Result
1 Open any page Header shows Journal, Map, Stats nav links
2 Click Map Navigates to /map

Automation: grep base template output for "/map" nav link


TC-2.5: Empty state (no GPS entries)

Step Action Expected Result
1 Remove lat/lng from test entry temporarily
2 Visit /map Map at world zoom, "No locations yet" message shown
3 Restore lat/lng

Manual verification required: Requires temporarily editing entry


TC-2.6: Map page is full-height on mobile

Step Action Expected Result
1 Open /map on mobile browser Map fills screen below header
2 Pinch to zoom Map zooms without page scrolling
3 Pan with finger Map pans without page scrolling

Manual verification required: Touch interaction requires physical device


Milestone 3 — Statistics Page

TC-3.1: Stats page loads

Step Action Expected Result
1 GET http://localhost:8081/stats HTTP 200
2 Inspect HTML Four stat blocks present

Automation: curl + HTTP status + grep for "stat-block"


TC-3.2: Days on the road count

Step Action Expected Result
1 curl /stats Page HTML
2 grep for "days" Shows "1 day on the road" (entry date: 2026-06-17, today: 2026-06-18)

Automation: grep stat-value output and compare to expected day count


TC-3.3: Entries count

Step Action Expected Result
1 curl /stats grep for "entry posted"

Automation: grep for "entry posted"


TC-3.4: Countries visited

Step Action Expected Result
1 curl /stats grep for "Netherlands"
2 grep for "country visited" Shows "1 country visited"

Automation: grep output


TC-3.5: Distance shows "—" for single GPS point

Step Action Expected Result
1 curl /stats grep for GPS_POINTS
2 In browser, check stat-distance Shows "—" (JS computes, needs browser)

Automation: grep GPS_POINTS array length from page source


Step Action Expected Result
1 Open any page header "Stats" link present in nav
2 Click Stats Navigates to /stats

Automation: grep any page HTML for "/stats" in nav


Milestone 4 — Mini-map on Tracker Feed

TC-4.1: Mini-map appears on tracker feed

Step Action Expected Result
1 GET http://localhost:8081/tracker HTTP 200
2 grep for "feed-map" Mini-map div present
3 grep for "FEED_ENTRIES" JSON array with Amsterdam entry
4 grep for "View full map →" Link to /map present

Automation: curl + grep


TC-4.2: Mini-map hidden when no GPS entries

Step Action Expected Result
1 Remove lat/lng from example entry
2 curl /tracker No "feed-map" div in output
3 Restore lat/lng

Manual verification: Requires temporarily editing entry


TC-4.3: Marker click navigates to entry (mobile)

Step Action Expected Result
1 Open /tracker on phone Mini-map renders above entry list
2 Tap Amsterdam marker Navigates to /tracker/2026-06-17.entry

Manual verification required: Touch interaction requires browser


TC-4.4: Entry list still visible below mini-map

Step Action Expected Result
1 Open /tracker Mini-map shows, scroll down

Manual verification required: Visual layout check


Post Submission Flow

These scenarios cover the full round-trip: filling the form → saving → verifying values in the UI and on disk. Use the exact test values specified so that each assertion can be precise.

Test data (use verbatim):

Field Value
Title QA Test Entry
Date & Time 2026-06-18 10:00
Content This is the QA test body. Second sentence for length.
City Tokyo
Country Japan
Latitude 35.689487
Longitude 139.691711
Photos none (keep simple for first run)

Expected slug: 2026-06-18-1000-qa-test-entry Expected folder: 2026-06-18-1000-qa-test-entry.entry/ Expected URL: /tracker/2026-06-18-1000-qa-test-entry.entry

The slug is built from date(Y-m-d-Hi) + title lowercased with [^a-z0-9]+ replaced by hyphens.


TC-P.1: Post form requires authentication

Step Action Expected Result
1 Open private/incognito tab (no session)
2 GET http://100.96.115.96:8081/post Page loads at /post URL (no redirect) but renders the login form inline
3 Inspect page content Login form fields (username, password) visible; post form fields absent

Automation: curl /post without auth; assert login-form-nonce present AND data[title] absent


TC-P.2: Post form renders all fields

Step Action Expected Result
1 Log in at /login Redirected to /tracker
2 Navigate to /post Post form page loads (200)
3 Check form fields present Title, Date & Time, description textarea, Photos upload
4 Check location fields Latitude, Longitude, City, Country inputs visible
5 Check action buttons 📍 Get Current Location and 🌤 Get Weather buttons visible
6 Check submit button Post Entry button visible
7 Check date field default Pre-filled with today's date and time (not blank)

Automation: curl /post with auth; grep for data[title], data[lat], data[location_city], get-location, get-weather


TC-P.3: Required field validation

Step Action Expected Result
1 Log in and open /post Form loads
2 Leave Title blank, fill in only the description
3 Submit form Page reloads with validation error on Title
4 Error message Indicates title is required
5 Fill in Title, clear Description/Content, submit Validation error on Content field
6 Confirm No new entry file created in pages/01.tracker/ during failed submissions

Manual verification required: Validation feedback requires browser


TC-P.4: Successful post submission — all fields

Step Action Expected Result
1 Log in and open /post Form loads
2 Enter Title: QA Test Entry
3 Set Date to 2026-06-18 10:00
4 Enter Content: This is the QA test body. Second sentence for length.
5 Enter City: Tokyo, Country: Japan
6 Enter Latitude: 35.689487, Longitude: 139.691711
7 Leave Photos empty
8 Click Post Entry Form submits (POST to /post)
9 Observe result Success message Entry posted successfully! shown on page
10 Form state Form is reset / fields cleared

Manual verification required: Form submission and success message require browser


TC-P.5: Entry file created on disk with correct values

Step Action Expected Result
1 After TC-P.4 completes
2 Check directory user/pages/01.tracker/ Folder 2026-06-18-1000-qa-test-entry.entry/ exists (add-page-by-form appends template name per physical_template_name: true)
3 Read user/pages/01.tracker/2026-06-18-1000-qa-test-entry.entry/entry.md File exists
4 Verify frontmatter title Equals QA Test Entry
5 Verify frontmatter date Equals 2026-06-18 10:00
6 Verify frontmatter location_city Equals Tokyo
7 Verify frontmatter location_country Equals Japan
8 Verify frontmatter lat Equals 35.689487
9 Verify frontmatter lng Equals 139.691711
10 Verify frontmatter template Equals entry
11 Verify frontmatter published Equals true
12 Verify page body Contains This is the QA test body. Second sentence for length.

Automation: Read file from filesystem; parse YAML frontmatter; assert each field value exactly


TC-P.6: Entry appears in tracker feed

Step Action Expected Result
1 BUG-001 fixed — no manual cache clear needed
2 GET http://100.96.115.96:8081/tracker Page loads (200)
3 Entry card present Card with title QA Test Entry visible
4 Date shown on card 18 Jun 2026
5 Location badge on card 📍 Tokyo, Japan visible
6 Entry card link href points to /tracker/2026-06-18-1000-qa-test-entry.entry
7 Excerpt shown Partial text of the body content visible

Automation: curl /tracker; grep for "QA Test Entry", "18 Jun 2026", "Tokyo", "Japan", "/tracker/2026-06-18-1000-qa-test-entry.entry"


TC-P.7: Entry detail page shows correct values

Step Action Expected Result
1 GET http://100.96.115.96:8081/tracker/2026-06-18-1000-qa-test-entry.entry Page loads (200)
2 Page title QA Test Entry in <h1>
3 Date header Thursday, 18 June 2026 (or locale equivalent)
4 Location badge 📍 Tokyo, Japan
5 Body content Full text This is the QA test body. Second sentence for length. rendered
6 No gallery Photo gallery section absent (no photos were uploaded)
7 Back link ← Back to journal link present, points to /tracker

Automation: curl /tracker/2026-06-18-1000-qa-test-entry.entry; grep for "QA Test Entry", "Tokyo", "Japan", "This is the QA test body", "Back to journal"


TC-P.8: Entry appears on map and mini-map

Step Action Expected Result
1 GET http://100.96.115.96:8081/tracker Mini-map section visible
2 Inspect FEED_ENTRIES JSON Contains entry with lat: "35.689487", lng: "139.691711", title: "QA Test Entry"
3 GET http://100.96.115.96:8081/map Map page loads
4 Inspect ENTRIES JSON Contains same entry

Automation: curl /tracker and /map; grep FEED_ENTRIES and ENTRIES JSON for lat/lng values


TC-P.9: Entry appears in stats

Step Action Expected Result
1 GET http://100.96.115.96:8081/stats Page loads (200)
2 Entries count Shows 2 entries (existing test entry + new QA entry)
3 Countries list Japan and Netherlands both listed

Automation: curl /stats; grep entry count and country names


TC-P.10: Two posts on the same day

Step Action Expected Result
1 Submit a first post: date 2026-06-18 10:00, title Morning Update Success message shown
2 Submit a second post: date 2026-06-18 14:30, title Afternoon Update Success message shown
3 Check filesystem Two separate folders exist: 2026-06-18-1000-morning-update.entry/ and 2026-06-18-1430-afternoon-update.entry/
4 Visit /tracker Both entries visible as separate cards

Note: The slug encodes date + time + title, so same-day posts are fully supported as long as they have different times or titles. A true collision (same date, same time, same title) would silently fail — treat this as acceptable given solo use.

Manual verification required: Requires two browser submissions


Cross-cutting Tests

Page Expected nav links
/tracker Journal, Map, Stats
/map Journal, Map, Stats
/stats Journal, Map, Stats
/tracker/2026-06-17.entry Journal, Map, Stats

Automation: curl each page, grep for all three links


TC-X.2: All pages return 200

Page Expected HTTP status
/ (redirects to /tracker) 200 or 302→200
/tracker 200
/tracker/2026-06-17.entry 200
/map 200
/stats 200
/post 200 (after login) or 302 (login redirect)

Automation: curl HTTP status checks


TC-X.3: Mobile touch targets ≥44px

Element Expected min height/width
Nav links 44px height
Gallery thumbnails 44px on shortest side
Lightbox close/prev/next buttons 44px
Post form buttons 44px height
"Get Location" button 44px height
"Get Weather" button 44px height

Manual verification required: Inspect computed CSS or measure visually on device


TC-X.4: No JS errors in browser console

Page Expected
/tracker No console errors
/map No console errors (may have tile 404s for tiles not in viewport — acceptable)
/stats No console errors
/tracker/2026-06-17.entry No console errors

Manual verification required: Open browser DevTools


Visual Design QA — Redesign Checklist

Design spec: user/docs/design/design-spec.md
Implementation plan: docs/working/plans/2026-06-18-ui-redesign.md

Typography

  • DM Serif Display loads for: entry titles, page headings (h1), stat numbers, site title
  • DM Sans loads for: body text, nav links, labels, form fields, timestamps
  • No fallback font (Georgia / system-sans) visible in place of custom fonts
  • Body text font-size ≥ 16px (no iOS zoom on form focus)

Colors

  • Page background is warm paper (#F7F5F2), not pure white
  • All links and CTAs use teal (#1F6B5A), not blue (#0066cc)
  • Active nav link is teal and bold
  • Map markers and route polylines are teal

Header

  • 3px teal border-top visible at top of header
  • Site title renders in DM Serif Display ("into the east")
  • Header sticks to top on scroll
  • On 320px viewport: title and nav both visible without overlap

Entry feed cards

  • Cards with photos show full-bleed 16:9 image with rounded corners
  • Date + location text overlay visible on gradient at bottom of photo
  • Entry title below photo in DM Serif Display
  • Subtle photo scale animation on hover (desktop)
  • Cards without photos show date/location meta row above title
  • "Read entry →" link is teal

Single entry page

  • If entry has photos: hero image spans full content width, max 480px tall
  • Entry title in DM Serif Display at large size (~48px desktop)
  • Thin border rule separates header from body text
  • Body text at 18px (--text-md)
  • "← Back to journal" footer link in teal

Post form

  • Lat/lng inputs NOT visible (hidden by CSS :has() selector)
  • Inputs have rounded corners and correct border
  • Focus ring on inputs is teal, not default browser blue
  • "Post Entry" submit button is teal, full-width, ≥52px height
  • After tapping "Get Location": status line shows "✓ Location captured · lat, lng" in teal
  • After tapping "Get Weather": status line shows "✓ Weather set · desc · temp°C" in teal
  • On error: status line shows in brick red, not teal

Stats page

  • Page heading "Trip Statistics" in DM Serif Display
  • Stat numbers in DM Serif Display, teal color
  • Stat cards on white background (not paper), with subtle shadow
  • Labels uppercase, muted gray, small

Map page

  • Map fills viewport below header with no gap
  • Map container height uses CSS variable (not hardcoded 61px)
  • Markers are teal circles (not blue)
  • Route polyline is teal

Mobile (375px viewport)

  • All pages scroll without horizontal overflow
  • Header title and nav fit in one row
  • Entry card photo fills full width
  • Post form buttons are thumb-reachable (44px+ targets)
  • Map page: map pans without page scrolling underneath (touch-action)

Accessibility

  • Focus ring visible on all interactive elements (keyboard navigation)
  • With prefers-reduced-motion: no animations/transitions fire

Mobile UX — Session 2026-06-21

These test cases cover the improvements made in session 2026-06-21.

Context: All changes target trip.html.twig, home.html.twig, and dailies.html.twig. Entry HTML is now shared via partials/entry-journal.html.twig and partials/entry-story.html.twig. Use the demo trip at /trips/italy-2026-demo — it has controlled content (12 journal entries + 4 stories + 7 GPX files). The home page is at /home. The active trip is currently us-canada-mex-2024.


TC-M.1: Back-to-top button appears on scroll

Step Action Expected Result
1 Open http://localhost:8081/trips/italy-2026-demo in desktop or mobile browser Page loads; no back-to-top button visible
2 Scroll down past ~80% of viewport height ↑ Top button appears in the bottom-right corner
3 Scroll back up near the top Button disappears
4 Scroll down again, then click ↑ Top Page scrolls smoothly to the top; button disappears
5 Check URL bar after clicking top No #entry-* hash in URL (the button clears any leftover hash)

Manual verification required.


TC-M.2: Map marker click scrolls entry below sticky header

Step Action Expected Result
1 Open http://localhost:8081/trips/italy-2026-demo Trip page loads with MapLibre mini-map above the feed
2 Click any map marker Page scrolls to the matching journal entry
3 Check scroll position Entry title is fully visible below the sticky site header — not hidden behind it
4 Check URL bar Hash updated to #entry-<slug>
5 Repeat on mobile (375px viewport) Same result — title fully visible below header on small screen

Manual verification required on both desktop and mobile.


TC-M.3: Map marker click highlights matched entry

Step Action Expected Result
1 Click a map marker Page scrolls to entry; after ~350ms the matched entry card shows a teal-tinted background
2 Highlight fades out Over 700ms the teal background fades to transparent; no permanent style change after

Manual verification required.
Implementation detail: CSS animation card-highlight (0%: teal-tinted bg → 100%: transparent). Class is-highlighted is added 350ms after click (after scroll settles) and removed after 700ms.


TC-M.4: PhotoSwipe lightbox covers full viewport on mobile

Step Action Expected Result
1 Open a trip page on a mobile browser (iOS Safari or Android Chrome)
2 Scroll down so the browser address bar hides (viewport visually expands)
3 Tap a photo to open the lightbox Lightbox background covers the full visible area — no gap at the bottom
4 Repeat on desktop Firefox and Chrome Lightbox covers the full window — no regression on desktop

Manual verification required on a physical device.
Root cause fixed: .pswp { height: 100dvh }100dvh tracks the live viewport dynamically; 100vh was freezing at the initial height before the address bar hid.


TC-M.5: PhotoSwipe keyboard arrow animation (fullscreen lightbox)

Step Action Expected Result
1 Open a journal entry that has multiple photos; click the expand icon Lightbox opens fullscreen
2 Press arrow key Next image slides in smoothly from the right with a fade; no instant jump
3 Press arrow key Previous image slides in smoothly from the left with a fade
4 Press rapidly several times Each keypress restarts the animation from scratch; no stuck/broken state
5 Navigate with mouse drag or touch swipe Swipe animation unchanged — the fix only affects keyboard navigation
6 Repeat steps 24 in Firefox Same smooth animation — Firefox Linux was the original repro environment

Manual verification required; must test in Firefox specifically.
Root cause fixed: pswp.currSlide.el is undefined in the PhotoSwipe v5 Slide class — .el is a property of the itemHolder wrapper, not the Slide. Changed to pswp.currSlide.container (the .pswp__zoom-wrap element), which is the correct Slide DOM reference.


TC-M.6: Photo strip prev/next arrows on pointer devices

Step Action Expected Result
1 Open http://localhost:8081/trips/italy-2026-demo on a desktop with a mouse Journal entries with multiple photos show / arrow buttons positioned outside the photo strip
2 Click Strip scrolls smoothly to the next photo
3 Click Strip scrolls back to the previous photo
4 Inspect DOM Arrow buttons (div.strip-controls) are siblings of .journal-photo-wrap, not children — they are outside the overflow:hidden container
5 Open the same page on a touchscreen (phone or touch-only tablet) Arrow buttons are not rendered / hidden; dot navigation still works

Manual verification required.
Note: Arrows are hidden via @media (hover: none) — the rule targets touch-primary devices, not screen width. A desktop touchscreen monitor would also hide arrows.
Root cause of earlier regression: Arrows were being inserted inside .journal-photo-wrap (which has overflow: hidden) and were clipped. Fixed by inserting them after the wrap element.


TC-M.7: Entry partials consistent across all three feed pages

Step Action Expected Result
1 Open http://localhost:8081/trips/italy-2026-demo (trip page) Journal entries and story cards render correctly
2 Open http://localhost:8081/trips/italy-2026-demo/dailies Identical entry markup — same photo strips, dots, expand button
3 Open http://localhost:8081/home (home page, currently in between-trips mode — see note)
4 On trip page and dailies: tap photo expand icon PhotoSwipe lightbox opens identically on both
5 On trip page and dailies: click a map marker Scrolls to entry with correct header offset on both
6 On trip page and dailies: keyboard arrows in lightbox Animation works on both

Manual verification required on trip page and dailies.
Note on home page: The home page only renders the journal feed in trip mode (when active_trip is set and the trip is ongoing). Currently active_trip is us-canada-mex-2024 and the trip may be in between-trips mode, so the home page may not show a journal feed. Verify by checking http://localhost:8081/home — if it shows the between-trips highlights grid, the home page feed cannot be tested without switching active trip or adjusting the trip dates.
Context: Entry HTML was extracted into shared partials (partials/entry-journal.html.twig, partials/entry-story.html.twig) and included from all three templates.