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: 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