Skip to main content

How to Test Calendar Booking and Scheduling

Short answer

Scheduling UIs fail on timezone-shifted slots, race double-bookings, buffer rules, and resource capacity—not on whether a calendar renders. Seed slot inventory per runId, use Playwright clock for same-day boundaries, probe booking rows for exclusivity, and test parallel booking conflicts explicitly.

Part of Testing Guides by UI patterns.

Who this is for​

Teams shipping appointment booking, meeting schedulers, resource calendars, or clinician availability with FullCalendar, Cal.com-style flows, or custom slot pickers. Healthcare, services, and B2B meeting tools.

Why testing calendar scheduling matters​

Scheduling errors destroy trust immediately:

  • Double booking — two clients same slot; HIPAA and SLA incidents in healthcare.
  • Timezone errors — patient arrives wrong hour; pair with datetime guide.
  • Buffer violations — 15-minute back-to-back when policy requires 30; provider burnout and billing issues.
  • Capacity limits — group class overbooked; refunds and reputational harm.
  • Cancellation windows — late cancel allowed when policy forbids; revenue leakage.

Probe booking exclusivity, UTC instants, and capacity counters—calendar cell color is not proof.

Complexity map​

ScenarioEdge caseWhy tests breakApproach
Double-book raceTwo tabs same slotBoth confirmParallel requests + probe
Timezone displayUser vs resource TZWrong labelProbe UTC + display
DST dayMissing slotClick failclock boundary spec
Buffer timeAdjacent slot hiddenPolicy bypassProbe gap minutes
Resource poolAny room vs specificWrong assetProbe resource_id
Recurring seriesException datesOrphan instanceProbe series id
WaitlistSlot freedPromotionInject cancel + probe
Blackout datesHolidaySlot shownNegative click
Min noticeBook 1h aheadShould blockclock + probe 422
Group capacity9/10 seatsOverbookProbe seats_remaining
External syncGoogle Calendar lagStale free/busyMock sync probe
Cancel/rescheduleOld slot freedDouble capacityProbe slot counts

Seed slot inventory​

await request.post('/api/test/seed-calendar', {
data: {
runId,
providerId: 'dr-1',
slots: [
{ startUtc: '2024-06-15T14:00:00Z', capacity: 1 },
{ startUtc: '2024-06-15T15:00:00Z', capacity: 1 },
],
},
});

Happy path booking​

await page.goto('/book/dr-1');
await page.getByRole('button', { name: /Jun 15.*2:00 PM/i }).click();
await page.getByRole('button', { name: 'Confirm' }).click();
const booking = await request.get(`/api/test/bookings?runId=${runId}`).then(r => r.json());
expect(booking).toHaveLength(1);
expect(booking[0].startUtc).toBe('2024-06-15T14:00:00Z');

Double-booking negative (critical)​

const slot = '2024-06-15T14:00:00Z';
const [r1, r2] = await Promise.all([
request.post('/api/test/book', { data: { runId, slot, client: 'a' } }),
request.post('/api/test/book', { data: { runId, slot, client: 'b' } }),
]);
const statuses = [r1.status(), r2.status()];
expect(statuses.filter(s => s === 201)).toHaveLength(1);
expect(statuses.filter(s => s === 409)).toHaveLength(1);
const bookings = await request.get(`/api/test/bookings?runId=${runId}&slot=${slot}`).then(r => r.json());
expect(bookings).toHaveLength(1);

Run UI parallel spec too: two Playwright contexts racing Confirm.

Requirement slices to cover​

  • timezone — booker and resource
  • slot_type — initial, follow-up, telehealth, group

Healthcare portals: see healthcare guide for PHI-safe synthetic patients.

CI checklist​

  1. Unique calendar per runId—parallel workers never share last slot
  2. Double-book race spec (API + optional UI)
  3. clock.install for min-notice and DST specs
  4. Probe UTC on every booking
  5. Cancel/reschedule frees slot—probe count
  6. Group capacity when product supports classes

Anti-patterns​

Anti-patternWhy it failsBetter approach
Shared "last slot" in CIParallel false 409Per-run inventory
Assert cell CSS colorTheme flakeProbe booking row
Skip race testProd double-bookPromise.all book
Local string time onlyTZ bugsUTC probe
waitFor calendar paintSlowclick slot + probe
Ignore cancel windowPolicy bugclock + negative

Example scenario

Situation: Two patients click the last available telehealth slot within 200ms.

Expected outcome: Exactly one booking; second user sees slot unavailable; no duplicate clinician event.

Why UI-only automation breaks: Second user sees error toast but probe shows two bookings—calendar sync corrupt.

  1. Arrange: Seed one remaining slot at 2024-06-15T14:00:00Z for runId.
  2. Act: Parallel UI confirm or API book race.
  3. Assert: Probe bookings.length=1; second response 409; slot marked unavailable in inventory probe.

TestChimp workflow: Instrument booking_attempt with slot_type and timezone; evolve telehealth slice if prod share grows.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow​

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with scenario annotations for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references​

Frequently asked questions

How do I prevent double-booking in parallel tests?

Use per-run calendar seeds; run API race with Promise.all expecting one 201 and one 409; optional dual-browser UI race on last slot.

How do timezones affect slot pickers?

Probe stored UTC always; display strings vary by user timezone—use clock and seeded user TZ from datetime guide patterns.

How do I test minimum booking notice?

Install clock near slot boundary, attempt book inside forbidden window, probe 422 and UI error—no booking row created.

Group classes vs 1:1 slots?

Seed capacity>1, book until full, assert next book fails with probe seats_remaining=0.

Cancel and reschedule?

Book, cancel via UI, probe slot available again; reschedule moves booking id without duplicate occupancy.

Which slot_types need TrueCoverage priority?

Compare prod booking_attempt distributions—telehealth and weekend slots often missed in generic weekday office-hour tests.

Healthcare-specific concerns?

Synthetic patients only; never real PHI in URLs or probes; pair with HIPAA-aware fixtures in healthcare vertical guide.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo