QA Report — Restful-booker-platform demo ("Shady Meadows B&B")
Run ID: 20260811-154910 Target: http://localhost:3003 Testing level: Exploratory (whole app, admin credentials in use) Date: 2026-08-11 Pipeline: Intake → Explore → Plan → Test → Critique → Report (v0.2.0)
Summary
30 scenarios were planned and executed. As logged by the Tester: 4 passed (S5, S11, S13, S21), 26 failed, 0 blocked.
Applying the Critic's binding audit (68 findings reviewed, 28 re-verified live) changes the picture in two important ways:
- One "critical" claim did not hold up and is withdrawn (S4 — "the calendar is entirely non-interactive"). The calendar is a drag-to-select control; the Tester never attempted a drag. Every price computation S4 actually checked was correct. The scenario's real, much smaller finding — a single click is silently ignored with no hint that a drag is required — survives as a low-severity UX issue.
- S21's "pass" verdict is itself disputed. The Critic flags that S21's own plan defines failure as "a field the admin plainly needs to correct (price, guest contact details) cannot be edited anywhere in the app" — and that condition was met (there is no admin path to fix a booking's price or a guest's email/phone) but the Tester still verdicted the scenario a pass. This is promoted into the issues list below as a medium-severity workflow gap.
A word on the 26/30 fail count: several "fail" scenarios (S1, S12, S24, S29 among them) contain only a single low-severity finding alongside otherwise-correct behaviour — the Tester's pass/fail axis means "at least one defect found," not "the capability is broken." Read the severity distribution below, not the raw fail count, as the signal of how healthy each capability is.
After applying the Critic's corrections, this run stands behind 40 confirmed issues (6 critical, 10 high, 13 medium, 11 low) and 4 withdrawn findings (documented in their own section, not deleted). The most serious, live-reproduced problems are: guests can create bookings dated 6+ years in the past with no validation; a duplicate/overlapping booking submission crashes the entire booking page instead of showing an error; any direct link to a reservation page without query parameters hangs forever; the guest homepage only ever shows 3 rooms no matter how many exist (a newly created, fully-populated room is permanently invisible to guests); the branding editor cannot be saved at all until an admin notices and fixes a field they never touched; and navigating directly to a deleted room's page crashes the app.
Issues
Ordered by severity. "Critic-confirmed" means the Critic independently re-reproduced the finding live (via the browser, the API, or the DOM) in its own session, as recorded in critique.json; items marked "not independently re-driven" were accepted on the Tester's own reasoning/evidence without a fresh live repro (still credible, but a single-observation record).
Critical
C1. Guest homepage lists only the first 3 rooms — a 4th seeded room and every newly created room are permanently invisible to guests
- Scenarios: S17 (primary), also observed in S2, S3, S16
- Critic status: Confirmed, mechanism pinned down live (
reword) - Repro: Create a fully-populated room via the admin Create form (or note the pre-existing 4th seeded room, id 4, room number "102"). Reload the guest homepage.
- Expected: Every room in the system appears in "Our Rooms."
- Actual: The homepage always renders exactly 3 cards. The Critic proved this is a hard 3-card cap, not a filter on incomplete records: it created a fully-described test room (real description, valid local image, price) and it still never appeared; when a dated availability query excluded room 1, the homepage showed rooms 2/3/4 — i.e. "the first three of whatever list
/api/room returns," not a fixed seed list. - Symptoms in this run: room id 4 (S2/S3/S16) and admin-created room "QA901" (S17) are both fully bookable via a direct
/reservation/{id} link, both editable in admin, but neither is ever discoverable by a guest browsing normally. - Evidence: screenshots/S2-reservation4.png, screenshots/critic-S17-homepage-3-of-8-rooms.png
C2. Bare /reservation/{id} URL (no query params) hangs on an infinite loading spinner
- Scenario: S3
- Critic status: Confirmed, reproduced in a fresh session with a >2-minute patience window (
keep) - Repro: Navigate directly to
/reservation/1 (or 2, 3) with no checkin/checkout query parameters — the exact pattern a bookmark, shared link, or search-engine cache would use. - Expected: The booking widget renders the same way it does when arriving via the homepage (which always appends dates).
- Actual: The booking card (calendar, price, Reserve Now) never renders — an indefinite spinner — even though every API call the page makes (
/api/room/1, /api/branding, /api/report/room/1, /api/room) has already returned 200. Reproduced by the Tester on all three rooms across multiple sessions and independently reproduced by the Critic with a 120+ second wait and a confirmed-empty network queue. - Evidence: screenshots/S3-bareurl-hang-confirm.png, screenshots/critic-S3-bare-url-spinner-2min.png
C3. A conflicting/duplicate booking submission crashes the entire booking page
- Scenarios: S7 (primary), same crash also hit in S8 and S9
- Critic status: Confirmed end-to-end from the guest UI (
keep) - Repro: Submit a booking, then submit the identical (or overlapping) booking again — or rapid-double-click the Reserve Now button.
- Expected: A friendly "these dates are already booked" message, consistent with how 400 validation errors are handled elsewhere in the same form.
- Actual: The server correctly rejects the conflict (409), but the client's error-rendering code assumes every failed response carries a
bookingErrors array — a shape that only exists for 400s. Reading .length off the undefined field throws an uncaught TypeError in BookingForm.tsx:211, which crashes the React tree to a "This page couldn't load" interstitial. The server-side dedup itself works correctly (no duplicate record is ever created) — only the client crashes. - Evidence: screenshots/S7-rapiddblclick-result.png, screenshots/critic-S7-conflict-booking-page-crash.png
C4. Booking flow accepts and persists a stay dated over 6 years in the past
- Scenario: S8
- Critic status: Confirmed server-side, independent of any UI reading (
keep) - Repro: Load
/reservation/1?checkin=2020-01-01&checkout=2020-01-05 and submit valid guest details. - Expected: Rejected with an explicit "check-in date must be in the future"-style error.
- Actual: Price computes normally, "Booking Confirmed" appears, and the record persists to the admin bookings table. Corroborated independently:
GET /api/booking?roomid=1 returns bookingid 9, checkin 2020-01-01, checkout 2020-01-05. - Evidence: screenshots/S8-pastdate.png
C5. Branding form cannot be saved at all while the seeded logo field holds its own relative path — blocking every other branding edit
- Scenario: S23
- Critic status: Confirmed at the API layer, independent of the UI (
reword) - Repro: Open
/admin/branding and submit — even a completely unmodified resubmission of the seed data. - Expected: The form's own seed data should pass its own validation.
- Actual:
PUT /api/branding rejects any payload where logoUrl is the seeded relative path /images/rbp-logo.jpg with 400 "Url should be a correct url format", in a generic, non-field-attributed error. An admin who wants to fix, say, just the contact phone number is blocked until they separately notice and fix a field they never touched. The Critic reproduced this directly against the API (identical payload with only the URL made absolute succeeds with 200). - Caveat on reproducibility: the run itself replaced the seeded relative logo URL with an absolute one while restoring branding state, so this exact blocker will not reproduce on this same server instance until it is restarted with fresh seed data. The underlying validation rule (reject relative logo URLs) is still live and would refire on any future relative-path value.
- Evidence: none captured directly (verified via API round-trip); see
critique.json for the request/response pair.
C6. Direct navigation to a deleted room's /reservation/{id} crashes instead of a graceful not-found
- Scenario: S30
- Critic status: Confirmed on a room the Critic created and deleted itself, ruling out run-order artifacts (
keep) - Repro: Delete a room via the admin rooms list, then navigate directly to its
/reservation/{id}. - Expected: A graceful not-found state.
- Actual:
GET /api/room/{id} returns 500 for a missing room (rather than 404), and the client has no null-guard: room.features.map(...) throws in RoomDetails.tsx:45 on the undefined room object, crashing the whole page to "This page couldn't load." Any stale bookmark, shared link, or search-engine cache of a since-deleted room hits a hard crash. - Evidence: screenshots/S30-deleted-reservation-page2.png, screenshots/critic-S30-deleted-room-reservation-crash.png
High
H1. Reversed date range renders a negative price summary
- Scenario: S8 — Critic status: Confirmed (
keep) - Repro: Load
/reservation/1?checkin=2026-09-20&checkout=2026-09-15. - Expected: Rejected before any price is computed.
- Actual: Displays "£100 x -5 nights = £-500," Total £-460, with no warning. (Actual submission of this range was separately blocked by the same 409/crash path as C3, not by any check on the range itself.)
- Evidence: screenshots/S8-reversed.png
H2. "Check Availability" widget offers rooms whose overlap the booking flow then rejects via a crash
- Scenario: S9 — Critic status: Confirmed live; mechanism corrected (
downgrade/reword) - Repro: Set the homepage widget to a range that partially overlaps (rather than exactly matches) an existing booking, e.g. dates contained entirely inside a booked range, or overlapping only its start/end.
- Corrected mechanism: The Tester's original framing ("cosmetic, never filters at all") was too strong — the widget does call a dated availability endpoint. The real bug: the filter only excludes a room when an existing booking is entirely contained inside the requested window. Any partial overlap, or a request contained inside an existing booking, is not detected and the room is still offered with an active "Book now" link carrying the conflicting dates — which then hits the 409-crash (C3) when followed through.
- Evidence: screenshots/S9-dates-set.png, screenshots/critic-S9-availability-offers-booked-room.png
H3. Browser Back after Logout restores a cached admin page, including guest data
- Scenario: S14 — Critic status: Confirmed, reproduced twice, worse than originally logged (
keep) - Repro: Log in, log out, press the browser Back button.
- Expected: Back after logout should never restore an admin view.
- Actual:
/admin/rooms re-renders fully (all rooms, full Create form) from the back-forward cache with no re-redirect, persisting for several seconds. The Critic additionally reproduced this on /admin/room/1, where Back restores guest booking data (names James, Alice, Adele) to an unauthenticated visitor. - Evidence: screenshots/S14-back-cached-full.png, screenshots/critic-S14-back-after-logout-admin-rooms.png, screenshots/critic-S14-back-after-logout-guest-data.png
H4. Room numbers 101 and 102 are each duplicated across two distinct room records
- Scenario: S16 — Critic status: Confirmed, broader than logged (
reword) - Repro: Open
/admin/rooms. - Actual: Room id 2 and room id 4 both display "102"; room id 1 and room id 6 (the S18-created duplicate) both display "101." Every downstream reference to "Room: 101" or "Room: 102" (report labels, potential future booking displays) is genuinely ambiguous between two rooms with different prices/accessibility.
- Evidence: screenshots/S16-rooms-list.png
H5. Create-room form does not enforce room-number uniqueness
- Scenario: S18 — Critic status: Confirmed, record still exists (
keep) - Repro: Create a room with room number "101" (already in use by the seeded room).
- Actual:
POST /api/room succeeds (200); the duplicate coexists indefinitely alongside the original, compounding H4.
H6. Booking inline-edit mode shows the room's flat nightly rate, not the booking's actual persisted total
- Scenario: S20 — Critic status: Confirmed on two different rooms (
keep) - Repro: Open a room's bookings table; note the read-only Price for a multi-night booking, then click the pencil to edit it.
- Actual: James Dean's booking reads Price 400 (4 nights × £100) in the read-only view but Price 100 (just the flat nightly rate, non-editable) in edit mode; the same pattern reproduces on Erica Bowthorpe's booking (300 vs 150). Cancelling restores the correct 400, confirming the stored value is right and only edit mode's display is wrong — it is a systemic display bug affecting every booking, not a one-off.
- Evidence: screenshots/S20-james-edit-mode.png, screenshots/S20-erica-edit.png, screenshots/critic-S20-booking-edit-mode-price.png
H7. Repeated Backspace in the inline booking date field crashes the admin page
- Scenario: S22 — Critic status: Confirmed from a clean page with ordinary keyboard input (
keep) - Repro: Pencil-edit a booking, focus the check-in date field, press Backspace repeatedly.
- Actual: An unhandled React error — "Maximum update depth exceeded" (infinite
setState loop) inside BookingListing.tsx:96's DatePicker — crashes the entire admin page. Distinct from the C3 crash (different component, different error class — infinite render loop vs. undefined-property access) and triggered by a realistic admin keyboard action. The underlying booking record is left intact. - Evidence: screenshots/S22-debug-state.png, screenshots/critic-S22-datepicker-backspace-crash.png
H8. Reservation-page availability calendar never visually marks booked/unavailable dates
- Scenario: S26 — Critic status: Confirmed and upgraded from medium to high; mechanism corrected (
reword) - Repro: Navigate a room's availability calendar to a month with a confirmed booking on that room (e.g. Feb 2026, room 1).
- Corrected mechanism: Originally logged as a missing feature (no greyed-out styling). The Critic found it is a broken feature, not an absent one: the page fetches the room's "Unavailable" ranges and hands them to the calendar's
events prop in the wrong shape — nested inside a wrapper object ({report:[...]}) instead of being spread into the array — so react-big-calendar can never render them. Six fetched ranges, including one covering most of the currently-displayed month, are silently dropped; the only event that ever renders is the guest's own in-progress selection. - Evidence: screenshots/S26-reservation-feb2026.png, screenshots/critic-S26-calendar-no-unavailable-marking.png
H9. Icon-only admin action controls are unlabelled, keyboard-unreachable spans
- Scenario: S27 — Critic status: Confirmed directly from markup (
keep) - Repro: Inspect the delete, pencil-edit, and inline confirm/cancel icons on
/admin/rooms and /admin/room/{id}. - Actual: Every one is a bare
<span> (e.g. fa-remove roomDelete) with no role, tabindex, aria-label, or title — not Tab-reachable and not announced to screen readers. A keyboard-only or screen-reader admin cannot edit, delete, confirm, or cancel any booking or room record.
H10. Booking on a deleted room becomes an orphaned record, inconsistently displayed with a corrupted price
- Scenario: S30 — Critic status: Confirmed in the data layer (
keep) - Repro: Delete a room that still has a booking attached; check
/admin/room/{deleted-id} and /admin/report for the same period. - Actual:
GET /api/booking?roomid={id} still returns the booking after the room is gone. /admin/room/{deleted-id} renders a half-empty page (blank room fields, "No features added") but still shows the orphaned booking row with its Price corrupted to 0 (it can no longer look up the room's rate) — while the same booking is silently absent from the Report calendar for that period. The booking is neither cleanly cascaded nor coherently blocked; it just becomes inconsistent across two admin surfaces.
Medium
M1. Room id 4's image is broken (external, unreachable host)
- Scenario: S2 — Critic status: Confirmed (
keep). naturalWidth 0 on /reservation/4 and every "Similar Rooms" card referencing it; the src points at https://www.mwtestconsultancy.co.uk/img/room1.jpg, which 404s, instead of the app's own local /images/ assets used by rooms 1–3. Evidence: screenshots/S2-reservation4.png, screenshots/S2-reservation1-similar2.png
M2. Zero-night stay is accepted and charged at the display layer
- Scenario: S8 — Critic status: Confirmed (
keep). /reservation/1?checkin=2026-09-20&checkout=2026-09-20 shows "£100 x 0 nights = £0," Total £40 (the two flat fees), with no warning that the range is invalid. Evidence: screenshots/critic-S8-zero-night-summary.png
M3. Guest has no durable way to identify or revisit a completed booking
- Scenario: S10 — Critic status: Confirmed structurally; not re-driven end-to-end to avoid creating another test booking (single-observation, low artifact risk). The confirmation panel shows only the two dates — no room, price, contact echo, or reference number — there is no email confirmation and no "My Booking" affordance anywhere on the guest surface, and reloading the same reservation page shows no trace a booking exists. Evidence: screenshots/S10-confirmation.png
M4. Contact form has no duplicate-submission guard
- Scenario: S7 — Critic status: Confirmed; severity raised from low to medium ("under-rated at low for a public create-form with no dedup protection"). Two identical "Dup Message Test / QA-DUP-TEST" rows exist in the inbox, indistinguishable from the list view.
M5. Homepage widget silently accepts a reversed date range
- Scenario: S9 — Critic status: Confirmed (
keep). GET /api/room?checkin=2026-08-20&checkout=2026-08-10 returns 200 with a full room list; the UI shows no error for the same input. Evidence: screenshots/S9-reversed-dates.png
M6. Cancel on room edit leaves the discarded value on the read-only view until reload
- Scenario: S19 — Critic status: Confirmed with real keystrokes (
keep). After typing a new price and clicking Cancel, the read-only view displays the never-saved value (e.g. "130888") for 3+ seconds with zero network activity; the server was never touched (GET still returns the true value), but only a reload corrects the on-screen display — an admin trusting the screen after Cancel is actively misled. Evidence: screenshots/S19-cancel-stale-display.png, screenshots/critic-S19-cancel-shows-discarded-value.png
M7. Oversized room name (300 characters) triggers an unhandled HTTP 500 instead of a validation error
- Scenario: S18 — Critic status: Confirmed independently against the API (
keep). Other invalid create-room inputs return clean 400s with messages; this one falls through to an unhandled server error. Evidence: screenshots/S18-longname-result.png
M8. Rejected inline booking edits give the admin no feedback at all
- Scenario: S22 — Critic status: Consistent with four independently rejected edits in the log (single-observation, not independently re-driven). A reversed range, zero-night stay, blank names, and an overlapping range are all correctly rejected server-side, but the row is simply left stuck in edit mode with no error banner — the only way to discover the edit failed is noticing the row never left edit mode.
M9. Branding form accepts out-of-range map coordinates
- Scenario: S24 — Critic status: Confirmed and restored (
keep). PUT /api/branding with latitude 999 / longitude -999 returns 200 with no validation error.
M10. Admin message detail modal does not close on Escape
- Scenario: S27 — Critic status: Confirmed (
keep). The dialog takes focus correctly on open but survives an Escape keypress.
M11. Homepage date inputs and the contact-form Message textarea have no accessible name
- Scenario: S27 — Critic status: Confirmed (
keep). The check-in/check-out date textboxes and the message body render with no label, aria-label, or placeholder in the accessibility tree, unlike their labelled siblings (Name/Email/Phone/Subject).
M12. Destructive deletes (bookings, messages, rooms) fire instantly with no confirmation prompt
- Scenarios: S28, S29, S30 — Critic status: Independently confirmed for all three control families via a live
window.confirm spy (keep). No native confirm() call and no custom modal ever fires before the DELETE request; a single click permanently destroys the record. (Everything downstream of the delete — persistence, cascade correctness for bookings/messages, inventory release — works correctly; the only gap is the missing confirmation step.)
M13. No admin path exists to correct a booking's price or the guest's email/phone
- Scenario: S21 — Status: promoted by the Critic from a UX observation the Tester itself excused as "outside pass/fail criteria." S21's own plan defines this exact condition as a failure ("a field the admin plainly needs to correct — price, guest contact details — cannot be edited anywhere in the app"), and it was met: the inline booking editor exposes only name/deposit/dates. Price is not editable and email/phone are not shown at all in edit mode (see H6, and the related bookings-table gap in S20). If a guest mistypes their contact details, or a price needs manual correction, there is no way to fix it anywhere in the app. Surfaced by critique re-review, not an independent Tester finding.
Low
L1. Top-nav "Amenities" link targets a non-existent anchor
- Scenario: S1 — Critic status: Confirmed (
keep). Clicking it changes the URL to #amenities but document.getElementById('amenities') is null, so nothing scrolls. Evidence: screenshots/S1-homepage.png
L2. All homepage room-card images share alt="Single Room" regardless of actual room type
- Scenario: S2 — Critic status: Confirmed (
keep). A screen-reader user hears the wrong room type for 2 of 3 cards. Evidence: screenshots/S2-homepage-rooms.png
L3. Line breaks in a submitted contact message collapse in the admin detail modal
- Scenario: S12 — Critic status: Confirmed — the container computes
white-space: normal (keep). The raw text still contains the original newlines; only the rendering collapses them. Evidence: screenshots/S12-markdown-render.png
L4. Logout button is shown on the unauthenticated login screen; clicking it triggers a masked server 500
- Scenario: S15 — Critic status: Confirmed, both halves reproduce (
keep). /admin shows a Logout button even before authenticating; clicking it while unauthenticated fires POST /api/auth/logout → 500, silently swallowed by the client's redirect to /.
L5. Admin rooms list offers no sort/filter/search affordance
- Scenario: S16 — Critic status: Confirmed but severity lowered from medium to low ("a missing convenience feature on a four-row table," not a malfunction — the headers are static text and never claimed sortability).
L6. Homepage heading levels skip
- Scenario: S27 — Critic status: Confirmed (
keep). H1 → H3 ("Check Availability"), H2 ("Our Rooms") → H5 (room names), H3 ("Contact Information") → H5 (Address/Phone/Email).
L7. Branding form accepts an invalid postcode with no validation
- Scenario: S24 — Critic status: Confirmed and restored (
keep). "NOTAPOSTCODE123456" is accepted with a 200 and no format check.
L8. Guest email/phone captured at booking are absent from the admin bookings table
- Scenario: S20 — Critic status: Confirmed as logged (single-observation, not independently re-driven). Not permanently lost — retrievable via the auto-generated "You have a new booking!" admin message — but absent from the natural place (the bookings table) an admin would look.
L9. Reservation-page calendar's single click is silently ignored, with no hint that a drag-select is required
- Scenario: S4 — Status: residual finding after the Critic refuted the original "calendar is entirely non-interactive" critical claim (see Withdrawn Findings). The calendar is a react-big-calendar drag-to-select control (
onSelectSlot deliberately ignores single-slot selections, requiring slots.length > 1); a real drag changes the selection and price every time, confirmed by the Critic on two rooms. Nothing in the UI signals that a drag, not a click, is required — a real discoverability gap. Evidence: screenshots/critic-S4-drag-select-room2.png
L10. Reservation-page calendar month header doesn't jump to the month of a checkin date supplied via URL
- Scenario: S3 — Status: promoted from a Tester UX observation, confidence high. Loading
/reservation/1?checkin=2026-09-14 still shows "August 2026" as the calendar header even though the Price Summary correctly reflects the September dates — a guest who picked September dates on the homepage lands on a calendar showing the wrong month with their stay nowhere in view. Re-verified live by the Critic on two different checkin values.
L11. Newly created rooms carry a literal placeholder description visible to guests
- Scenario: S17 — Status: promoted from a Tester UX observation, confidence high. The create-room form has no description field, so every new room starts with the literal string "Please enter a description for this room" — currently live on rooms 4, 6, and 7. Guest-visible content, not just an admin-side inconvenience.
Withdrawn / Corrected Findings
These are not carried as issues in the main list above. Per the run's policy they are recorded here rather than silently deleted.
- W1 — S4: "The availability calendar does not respond to date-cell clicks at all; a guest cannot select or change dates" (originally critical). Refuted-artifact. The calendar IS interactive: it is a react-big-calendar with
selectable=true whose onSelectSlot handler deliberately ignores single-slot selections (if (result.slots.length > 1)), i.e. the app requires a click-drag across a range. The Tester tried ref-clicks and synthetic single-click event dispatches but never a drag. The Critic's live drags changed the selection and price summary every time on two different rooms (e.g. room 1 went from "£100 x 3 nights, £340" to "£100 x 4 nights, £440"). The residual real finding is carried forward as L9 above. - W2 — S6: "The guest booking form's validation-error banner intermittently fails to render after a rejected submission" (medium). Uncertain / not reproducible. The original observation was a single occurrence after a 2-second wait, and the Tester's own log noted repeat attempts "mostly displayed the error correctly." The Critic ran three clean attempts on fresh page loads with the same invalid phone input, each waiting 5 seconds: all three rendered the error correctly, with no crash and no empty-alert state. Not carried as a confirmed defect.
- W3 — S23: "Branding save does not persist" (originally logged as a defect). Refuted — reload artifact, not an app bug. The Tester's own methodology correction (retroactively applied within the same scenario) traced this to
agent-browser open serving a stale cached render of /admin/branding on this Next.js app, rather than re-fetching current data; reload and direct GET /api/branding calls both confirmed every save round-tripped correctly. The Critic independently re-verified this via a fresh PUT/GET pair. This is exactly the "reload artifact, not an app defect" pattern this pipeline is built to catch — it did not reach the Issues list. - W4 — S23 step 5: "Branding description/address propagation to the guest homepage is unverified" (open follow-up, not a defect claim). Refuted-misread — closed. The Critic loaded a fresh homepage and confirmed the description, address line 1, post town, contact phone, and contact email from the restored branding all propagate correctly. No propagation gap should be read into this run.
Two additional AppMap leads were investigated and found not to hold up, but were never logged as Tester defects in the first place, so no withdrawal is needed — noted here only for completeness: the AppMap's concern that the /admin "Invalid credentials" error is missing from the accessibility tree was self-corrected within S13 (it is exposed via role="alert"; the gap was in the -i interactive-only snapshot mode, not the app) and confirmed by the Critic across three forms (S6, S12, S13). The AppMap's concern that multi-night report-calendar bars "render only on the check-in day" was checked directly in S25 and found false — bars correctly span their full multi-night range, including across week-row boundaries.
UX & Quality Observations
Findings that are not defects but affect usability or invite future problems:
- Booking total vs. stored price (S5, flagged by critique as a medium-confidence, unconfirmed candidate). The guest is quoted and confirms against £340 (3 nights + £25 cleaning + £15 service), but the booking API stores no price field including those fees — the admin's Price column is nights × nightly rate only (£300), and the £40 in fees is unrecoverable from any admin surface. The Tester read this as a reasonable data-model split (room charge vs. add-on fees); the Critic flags it as worth the product owner confirming is intentional rather than an oversight. Not promoted to a confirmed issue — listed here as unconfirmed per the critique's own medium-confidence rating.
- Session lifetime is undocumented. The admin session appeared to expire between scenario boundaries (S5), requiring re-authentication with no stated session duration anywhere in the UI.
- Admin message body omits which room was booked. The auto-generated "You have a new booking!" message states the dates and guest contact details but never the room, so an admin working from the inbox alone cannot tell which room to prepare (S5).
- Booking-form validation messages are more generic than the contact form's. The booking form's "must not be empty" appears identically for both Email and Phone, and size-constraint messages don't name their field; the contact form, by contrast, consistently names the field ("Phone must be between 11 and 21 characters") (S6, S12).
- The 18-character Firstname limit (and similar field limits) is never surfaced in the UI — a guest only discovers it after a rejected submission (S6).
- Create-room and branding-form error feedback is a single generic message, never field-specific, unlike the guest-facing forms (S18, S23).
- The "Directions" field in the branding editor is actually a URL field but renders as a large free-text textarea seeded with description-like prose — a confusing field design that invites exactly the kind of mistaken entry made during testing (S23).
- No rate-limiting or lockout after repeated failed admin logins (S13) — flagged for awareness; out of scope for this project's security-testing exclusion.
- The admin bookings table (S20) and rooms list (S16) both offer no sort/filter affordance beyond default creation order — acceptable at current (small) scale.
- Report-calendar booking bars are styled and wired as clickable (cursor:pointer, a real onClick handler) but do nothing when clicked (S25) — the app never passes an
onSelectEvent handler to the calendar, so the click can never resolve to anything; a false affordance, confirmed live by the Critic. This is a genuine medium-severity finding, tracked separately (not double-counted in the Issues section above, since the calendar component's other defect — H8 — is the same component family; this one is the Report page's own instance). - Message rows require a specific click pattern to open — a semantic text-locator click worked reliably where a raw/dispatched click on the row
div silently no-op'd — worth the product team knowing the click target may be narrower than the row's visible bounds (S11).
Coverage & Caveats
What was tested: the full scope declared in the plan — guest homepage, all three original reservation pages plus the 4th/duplicate room, the end-to-end booking flow with contact details, the contact form, static policy pages, admin authentication (including the unauthenticated boundary on every admin route and browser Back after logout), admin rooms (list/create/edit/delete), admin bookings (per-room view, inline edit, delete, and consistency with the Report calendar), admin messages (inbox, detail modal, unread badge, delete), the branding editor (edit/save/persistence/propagation), the admin Report page (calendar rendering, month navigation, bar interaction), and cross-cutting checks (validation, duplicate submission, persistence after reload, link/media integrity, accessibility, time-dependence of date-relative views). All 30 planned scenarios executed to completion; none were blocked.
Excluded by design (per project scope, not a gap): payment flows (the app has none), direct REST-only testing without a UI path, load/performance profiling, multi-browser matrix testing, and security penetration testing.
Environment facts material to interpreting these results:
- Seeded booking data spans 2026-02-01 to 2026-03-05, while the run's system clock read 2026-08-11/12 — roughly six months later. Every date-relative view (the admin Report calendar, the homepage date widget, the reservation-page calendar) defaults to the current clock period and therefore opens empty of seeded data by default. All three offer working navigation controls back to the seeded period, and the Tester and Critic both used them (S25/S26 explicitly verified this recoverability) — no view was silently treated as broken just because it opened empty.
- Origin
localStorage/sessionStorage for localhost:3003 were cleared immediately before this run. - This run mutated live application state, as disclosed throughout the scenario logs and the critique: bookings, rooms, and messages were created and deleted (all using an obvious
QATest/QA--prefixed naming convention to distinguish them from seed data); the branding form was edited and restored. The Critic's own re-verification pass performed additional, disclosed mutations: created and deleted a test room (id 21, "CRITIC950"); deleted a leftover run-created room (id 7, "QANoFeature") as part of the delete-confirmation probe; attempted one conflicting booking that the server correctly rejected with no record created; wrote and restored branding coordinates/postcode (confirmed restored to 52.6351204/1.2733774, "N1 1AA"). Residue that was not cleaned up: room id 6 (the "101" duplicate created in S18) and room id 19 (the <b>QA</b>-named room from S18) remain in the system; the branding logoUrl remains the run's absolute-URL substitute rather than the original seeded relative path (this is why C5's exact blocker will not reproduce again without restarting the app with fresh seed data — see C5's caveat above). - Seeded records (rooms 101/102/103, the James Dean and Erica Bowthorpe bookings) were deliberately never edited or deleted destructively — only read-verified — so later scenarios that depended on them stayed valid throughout the run.
Known driver/tooling artifacts encountered and not counted as app defects: ref-clicks silently no-op when the target's cached bounding box is outside the current viewport (worked around with scrollIntoView before every off-screen click); the accessibility snapshot's interactive-only mode (-i) does not surface some visible static text (the login error banner), which the Tester initially misread as an accessibility gap and self-corrected in S13; message-row clicks required a semantic text-locator rather than a raw element click; native DatePicker fields needed keystrokes or JS value-setters with dispatched events.
Coverage gap identified by the Critic, not itself a defect: the admin Report calendar is also selectable, and dragging on it calls setDates() and opens an admin create-booking form — i.e. there is an admin-side booking-creation path that no scenario in this run exercised. This is an untested capability, not a tested-and-passed one, and would be a natural next-run addition.
Reporter's own severity sanity-check: the two highest-severity claim patterns this pipeline is specifically built to catch — "silently fails to persist" claims caused by a stale reload, and "value frozen/doesn't update" claims caused by reading the wrong data model — were checked against the top findings before inclusion. The one instance of the first pattern in this run (S23's "branding save does not persist") was already caught and downgraded to a withdrawn caveat by the Tester's own in-scenario correction and independently confirmed by the Critic (see W3); it is not present in the Issues list above. S20's edit-mode price mismatch (H6) was checked against the second pattern and ruled a genuine display bug, not a stale-data-source artifact: the read-only view and the edit-mode view were compared side-by-side on the same unchanged record, both are static reads with no reload or time-shift between them, and the Critic reproduced the exact same 400-vs-100 style gap on a second, independent room.