Travel & Booking QA
Seat selection, availability calendars, dynamic pricing, and booking race conditions.
// OVERVIEW
Every booking is a time-locked promise. Time-zones make the 'when' uncertain, concurrent holds make the 'what' uncertain, and supplier integration lag can make both wrong simultaneously — after the customer has already paid.
// What makes Travel & Booking QA different
- All availability is timezone-sensitive: a booking date is only correct relative to the departure timezone, not the user's browser timezone — a flight at 00:30 local time looks like the previous calendar day in UTC
- Availability is a transient held-and-expiring lock, not a permanent stock decrement: a seat is held for a short checkout window; if payment does not complete before the hold expires, the seat is released back to inventory
- Double-booking under concurrency: two sessions can hold the last available seat simultaneously, both proceed through checkout, and both receive a booking confirmation before the collision is detected
- Cancellation cutoff math is calculated against departure-local time, not server UTC — DST crossings mean a straightforward time-subtraction can produce the wrong refund eligibility decision
- Supplier availability is a cached snapshot: the live availability at the GDS can differ from the cached state shown to the user, causing confirmed bookings to be made against inventory that is no longer available
// Core user journeys
| Journey | What to cover |
|---|---|
| Search, hold, and confirm | Search availability, select a seat, enter the hold, complete checkout and payment, and receive a booking confirmation with the correct departure date and timezone |
| Cancellation within refund window | Cancel a booking with more than the policy-defined time remaining before departure; assert refund eligibility is calculated against departure-local time and the correct amount is returned |
| Booking modification | Change the departure date or seat on an existing booking; assert the original hold is released, the new hold is created, and any price difference is correctly applied |
| Seat selection and upgrade | Select a specific seat from the seat map; assert the seat is held for the authenticated user and not available to a concurrent session during the hold period |
| Supplier availability check and mismatch handling | Trigger a live availability query against the supplier API; assert that a mismatch between cached and live state is surfaced to the user before payment, not after confirmation |
// RISKS & TEST AREAS
// Main risk areas
| Risk | Why it matters |
|---|---|
| Double-booking last seat under concurrency | Two concurrent sessions both hold the same last available seat and both complete checkout before the collision is detected; both receive PNR confirmations for a single physical seat |
| Timezone date shift in confirmation | A flight at 00:30 local departure time booked from a browser in UTC+9 records the booking date in UTC, shifting it to the previous calendar day; the confirmation email shows the wrong departure date |
| Hold-expiry gap | A user's payment takes 65 seconds on a 60-second hold; the hold expires and the seat is released and re-assigned at 60 seconds; the payment captures at 65 seconds but no booking is created because the seat is gone |
| Cancellation cutoff miscalculation | Policy is expressed as '24 hours before departure in local departure time', but the system calculates the cutoff in server UTC; a customer who cancels within the refund window in local time may be incorrectly denied a refund |
| DST breaks flight duration display | A flight crossing a daylight-saving spring-forward boundary is displayed as 1 hour instead of 2 hours; the scheduled block time is correct but the wall-clock duration calculation uses the incorrect timezone offset |
// Functional areas to test
- Availability search and calendar: seat availability, date-range search, and price display across timezones
- Seat hold and expiry: transient lock creation, hold-window countdown, expiry release, and hold extension on payment retry
- Checkout and payment: multi-step form, payment capture within the hold window, and booking creation on payment success
- Cancellation and refund: cutoff calculation in departure-local time, refund amount calculation, and refund status updates
- Supplier API sync: cached availability vs live GDS state, mismatch detection before payment, and fallback messaging
- Timezone-aware date display: departure date, arrival date, and layover duration all expressed in the correct local timezone
// API & integration areas
- GDS/supplier availability API: assert cached inventory is refreshed before checkout confirmation and that stale cache state is never the basis for a final booking
- PNR and booking confirmation generation: assert the booking reference is created atomically with payment capture and that duplicate references are not generated under concurrent requests
- Cancellation and refund endpoint: assert refund eligibility is computed in departure-local time and that the refund amount matches the policy at the cancellation timestamp
- Booking-status webhook from supplier: assert status updates (e.g. airline cancellation) are reflected in the local booking state and that conflicting states are resolved deterministically
- Hold-expiry callback: assert that when a hold expires the seat is released back to available inventory immediately and is bookable by the next session
// Data testing
- Seed availability data spanning DST transition dates in multiple departure timezones — specifically spring-forward and fall-back boundary dates with departures near midnight
- Seed inventory with exactly one seat remaining for concurrency tests; reset after each test run to avoid state bleed between scenarios
- Seed bookings with cancellation deadlines that fall within 30 minutes of a DST transition to test edge-case cutoff calculations
- Use a supplier sandbox or mock GDS for all availability tests; never run booking tests against live supplier inventory
// CROSS-CUTTING CONCERNS
// Security & privacy
- PNR and booking reference entropy: references must not be sequentially guessable — assert that iterating adjacent IDs does not return other users' booking details
- Passenger personal data (passport number, date of birth, full name) must not appear in application logs, error messages, or analytics payloads
- Payment data is PCI-scoped: card numbers must never be stored or logged; assert payment capture uses tokenisation or a provider-hosted redirect
- Booking modification must verify authenticated ownership: assert a user cannot modify or cancel another user's booking via a direct API call with a known booking reference
// Accessibility
- WCAG AA contrast and keyboard navigation on date picker components and seat selection maps, including focus management when a seat is selected or becomes unavailable
- Full keyboard navigation through the multi-step booking flow: search → seat selection → passenger details → payment → confirmation
- Screen reader on dynamic seat-availability updates: when a seat becomes unavailable during the hold period, assert the change is announced via an ARIA live region
// Performance
- Availability search under concurrent load: simulate peak travel-season traffic patterns and assert search response latency stays within the defined SLA
- Seat-hold expiry release: assert the seat becomes bookable by the next session within the defined window after the hold expires, measured under concurrent load
- GDS latency graceful degradation: when the supplier API response exceeds the timeout threshold, assert the user sees a clear availability-check message, not a blank page or unhandled error
// Mobile & responsive
- Mobile booking at 375 px: date pickers, seat maps, and multi-step form fields must be touch-usable with correct tap-target sizes across all booking steps
- Push notification for supplier booking status change: assert the notification is delivered and correctly identifies the booking when the supplier confirms or rejects a pending booking
// BUGS & SCENARIOS
// Common bugs
| Bug | Scenario / repro |
|---|---|
| Double-booking last seat | Two concurrent browser sessions both hold the last available seat and both complete checkout; both receive booking confirmation emails with valid PNRs; the airline check-in system sees two PNRs assigned to one physical seat |
| Timezone date shift in confirmation | User in UTC+9 books a flight departing at 00:30 local airport time; the system stores the booking date in UTC (the previous calendar day); the confirmation email shows the wrong departure date, causing the passenger to arrive a day late |
| Hold-expiry gap | User's payment takes 65 seconds on a 60-second seat hold; the hold expires and the seat is released at 60 seconds; the payment provider captures at 65 seconds but no booking is created because the seat has already been re-assigned to another session |
| DST flight duration display | A flight departs at 01:30 on the morning clocks spring forward; the displayed duration is 2 hours (scheduled block time) but the actual wall-clock difference is 1 hour because the destination timezone moved forward during the flight |
| Cancellation refund miscalculation | Policy grants a full refund if cancelled more than 24 hours before departure in local departure time; system calculates the cutoff in server UTC; customer cancels when 23 hours remain in local time but 25 hours remain in UTC; system incorrectly grants a full refund |
// Example test scenarios
- 01Open two browser sessions simultaneously; both select the last available seat and proceed through checkout; complete both payment flows — assert exactly one booking is confirmed and the other receives a seat-unavailable error
- 02Book a flight departing at 00:30 local airport time from a browser configured to UTC+9 — assert the confirmation email shows the correct local departure date, not the UTC date which is one calendar day earlier
- 03Complete checkout after the seat hold has expired using a test-mode short TTL — assert no payment is captured and the user receives a clear hold-expired message with an option to restart
- 04Search for a flight crossing a DST spring-forward boundary — assert the displayed flight duration matches the actual wall-clock difference between departure and arrival, not the scheduled block time
- 05Book a cancellable ticket; cancel at a point where 23 hours remain in local departure time (server UTC shows 25 hours) — assert refund eligibility uses departure-local time and the full refund is correctly denied per policy
// Edge cases
- Airline cancels the flight after the user has completed checkout: the local booking stays 'confirmed' while the supplier marks it cancelled — assert the user receives a proactive notification and the booking state reflects the supplier update within the defined SLA
- Booking created at exactly the cancellation cutoff boundary: assert refund eligibility is deterministic and does not flip based on millisecond timing differences between the cancellation request and the cutoff calculation
- Multi-segment journey with legs crossing timezone boundaries: assert layover duration is expressed as clock time at the layover location, not as the arithmetic difference of two UTC timestamps
- Availability search at midnight on a DST transition date: the datetime range spanning the clock change must not produce a zero-length or negative-length window that returns no results
- Booking modification while hold expires: user initiates a seat change and the new hold expires during the modification form — assert the original booking is preserved intact and not cancelled
// AUTOMATION & TOOLS
// What to automate
- Concurrent last-seat test: fire two simultaneous hold requests and checkout attempts for the same single-seat inventory; assert exactly one booking is confirmed and one receives a seat-unavailable error
- Timezone parametrised matrix: book the same departure from N browser timezone configurations against M departure timezones; assert the confirmation date always uses departure-local time, not server UTC
- DST date matrix: seed bookings spanning spring-forward and fall-back boundary dates in multiple departure timezones; assert duration calculation and cancellation cutoff are correct on all boundary dates
- Hold-expiry test: create a hold with a test-mode short TTL; assert the seat becomes bookable again immediately after expiry and that no booking is created if payment arrives after the TTL
// Useful tools
PlaywrightE2E booking flows, multi-step checkout, mobile viewport seat-map testing, and concurrent-session race scenariosPostmanGDS/supplier API collections, hold-creation and expiry endpoints, cancellation and refund verificationBoundary value generatorGenerate cancellation cutoff boundary values: exactly at, 1 second before, and 1 second after the policy thresholdPairwise test case generatorTimezone × departure-timezone matrix and DST boundary date combinations without exhaustive enumerationk6 performance testingConcurrent availability-search load test and seat-hold expiry release timing under peak traffic patternsAPI testing checklistSupplier API integration coverage: GDS sync, PNR generation, hold-expiry callback, and booking-status webhook
// SHIP & LEARN
// Release readiness checklist
- Double-booking concurrency suite passes: only one booking is confirmed when two sessions compete for the last available seat
- Timezone date display correct in all confirmation paths: departure date always expressed in departure-local time, not server UTC
- Hold-expiry handled correctly: no payment is captured and no booking is created if payment arrives after the hold has expired
- DST duration calculation correct on spring-forward and fall-back boundary dates across all departure timezones in scope
- Cancellation cutoff uses departure-local time in all calculation paths: UTC-only calculation is blocked in code review and test
- GDS degraded-mode tested: slow or unavailable supplier returns a clear user message within the defined timeout, not a blank page or unhandled error
- Mobile booking flow validated at 375 px viewport: date pickers, seat maps, and payment form all usable with touch