Scheduling & Booking Testing Guide

How to Test Cal.com Booking with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Cal.com booking flows with Playwright. Event types, custom booking fields, timezone selection, the embed widget versus hosted pages, recurring event scheduling, redirect handling, and the pitfalls that silently break real booking test suites.

7M+

Cal.com powers over seven million bookings per month across thousands of organizations, with its open-source scheduling infrastructure used by companies ranging from solo consultants to enterprise teams.

Cal.com

0+Redirect hops per booking
0Booking scenarios covered
0Timezone edge cases
0%Fewer lines with Assrt

Cal.com Booking End-to-End Flow

BrowserYour AppCal.comCalendar APIEmail ServiceClick Book MeetingLoad booking pageRender event type + time slotsSelect date/time, fill fieldsCheck availabilityConfirm slot availableSend confirmation emailsRedirect to success page

1. Why Testing Cal.com Booking Is Harder Than It Looks

Cal.com is an open-source scheduling platform that replaces tools like Calendly. On the surface, a booking flow seems simple: pick a time, fill in your name and email, confirm. In practice, the flow involves several moving parts that make automated testing surprisingly complex.

First, the calendar date picker is highly dynamic. Time slots are fetched asynchronously based on the selected date, the host's connected calendar availability, and the viewer's detected timezone. A test that clicks a date before slots have loaded will fail intermittently. Second, Cal.com supports custom booking fields (phone numbers, dropdown selectors, text areas, radio buttons) that vary per event type. Your test cannot hardcode a fixed form structure because each event type may have a completely different set of required fields.

Third, the embed widget introduces an iframe boundary. When Cal.com is embedded in another application using the Cal.com embed snippet, every interaction must go through Playwright's frameLocator API. Selectors that work on the hosted page will silently fail inside the iframe. Fourth, recurring event booking requires the user to select multiple time slots across different dates, creating a multi-step form flow with running state that persists across calendar navigation. Fifth, Cal.com can redirect to a custom success URL after booking, which means the post-booking assertion target changes per event type configuration.

There are six structural reasons this flow is hard to test reliably: (1) async time slot loading tied to real calendar availability, (2) per-event-type custom fields with varying validation rules, (3) timezone detection and conversion affecting which slots appear, (4) iframe boundaries for embed deployments, (5) multi-step recurring booking state, and (6) configurable post-booking redirects that change the assertion target.

Cal.com Booking Flow

🌐

Select Event Type

15min, 30min, etc.

🌐

Pick Date

Calendar date picker

⚙️

Choose Time Slot

Async availability check

🌐

Fill Booking Form

Name, email, custom fields

⚙️

Submit Booking

POST to Cal.com API

Confirmation

Success page or redirect

Embed Widget vs. Hosted Page Decision

🌐

Your App

User clicks Schedule

↪️

Embed?

iframe or hosted link

📦

iframe Path

frameLocator required

🌐

Hosted Path

Direct page navigation

⚙️

Same Booking Flow

Date, time, fields, confirm

Callback/Redirect

Back to your app

A comprehensive Cal.com booking test suite needs to cover all of these surfaces. The sections below walk through each scenario with runnable Playwright TypeScript you can paste directly into your project.

2. Setting Up a Reliable Test Environment

Cal.com is open source, which means you can run a local instance for testing or use a dedicated Cal.com Cloud account. For the most reliable end-to-end tests, run Cal.com locally using Docker so you control the database, calendar integrations, and availability windows completely. If you test against Cal.com Cloud, create a separate test account and use dedicated event types that will not conflict with real bookings.

Cal.com Test Environment Setup Checklist

  • Create a dedicated Cal.com test account (or run locally via Docker)
  • Create test event types: 15-minute, 30-minute, and recurring
  • Add at least one custom booking field (phone number) to an event type
  • Set predictable availability windows (e.g., Mon-Fri 9am-5pm UTC)
  • Configure a test redirect URL for post-booking redirects
  • Generate a Cal.com API key for programmatic cleanup
  • Set up the Cal.com embed snippet in a test HTML page
  • Disable email notifications for the test account (or use a catch-all domain)

Environment Variables

.env.test

API Cleanup Between Test Runs

Cal.com bookings persist in the database. If your test creates a booking for a specific slot, that slot becomes unavailable for the next run. Use the Cal.com API to cancel and delete all test bookings before each suite. The GET /v1/bookings endpoint lists bookings, and DELETE /v1/bookings/:id removes them.

test/helpers/calcom-cleanup.ts

Playwright Configuration for Cal.com

Cal.com pages load time slots asynchronously and animate calendar transitions. Set a generous action timeout and configure the timezone explicitly so your tests produce deterministic results regardless of where CI runs.

playwright.config.ts
Cal.com Test Environment Setup

3. Scenario: Basic Event Type Booking (Happy Path)

The simplest Cal.com booking flow targets a standard event type (such as a 15-minute meeting) on the hosted booking page. The user navigates to the event type URL, selects a date from the calendar, picks an available time slot, fills in their name and email, and confirms the booking. The challenge is that time slots load asynchronously after date selection, so your test must wait for slots to appear before clicking one.

1

Basic Event Type Booking (Happy Path)

Straightforward

Goal

Starting from a Cal.com event type page, select a date, pick the first available time slot, fill in booker details, submit the booking, and confirm the success page renders with correct booking details.

Preconditions

  • Cal.com instance running at CALCOM_BASE_URL
  • Test event type 15min exists with open availability
  • No existing bookings blocking the next available slot

Playwright Implementation

calcom-booking.spec.ts

What to Assert Beyond the UI

  • The Cal.com API returns the booking in GET /v1/bookings with status accepted
  • The booked time slot is no longer available for the same date
  • The confirmation page URL contains the booking UID

Basic Booking: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('basic 15-minute booking', async ({ page }) => {
  await page.goto('/e2e-test-user/15min');

  const availableDay = page.locator(
    '[data-testid="day"][disabled!=""]'
  ).first();
  await availableDay.waitFor({ state: 'visible', timeout: 10_000 });
  await availableDay.click();

  const timeSlot = page.locator('[data-testid="time"]').first();
  await timeSlot.waitFor({ state: 'visible', timeout: 10_000 });
  await timeSlot.click();

  await page.getByLabel(/your name/i).fill('Test Booker');
  await page.getByLabel(/email/i).fill('test@example.com');
  await page.getByRole('button', { name: /confirm/i }).click();

  await expect(
    page.getByText(/this meeting is scheduled/i)
  ).toBeVisible({ timeout: 15_000 });
});
53% fewer lines

4. Scenario: Custom Booking Fields and Validation

Cal.com allows event type owners to add custom booking questions beyond the default name and email. These can be short text inputs, long text areas, phone number fields, single-select dropdowns, multi-select checkboxes, and radio button groups. Each field can be marked as required. Your test must handle the dynamic nature of these fields because different event types will present different forms.

The tricky part is validation. Cal.com validates custom fields client-side before submission. A phone number field expects a valid international format. A required dropdown must have a selection. If your test skips a required custom field, the booking form will not submit and no visible error may appear above the fold, causing the test to time out waiting for the confirmation page.

2

Custom Booking Fields and Validation

Moderate

Playwright Implementation

calcom-custom-fields.spec.ts

Custom Fields Booking: Playwright vs Assrt

test('booking with custom fields', async ({ page }) => {
  await page.goto('/e2e-test-user/intake-call');

  // Select date and time ...
  const availableDay = page.locator(
    '[data-testid="day"][disabled!=""]'
  ).first();
  await availableDay.waitFor({ state: 'visible' });
  await availableDay.click();
  const timeSlot = page.locator('[data-testid="time"]').first();
  await timeSlot.waitFor({ state: 'visible' });
  await timeSlot.click();

  await page.getByLabel(/your name/i).fill('Test Booker');
  await page.getByLabel(/email/i).fill('test@example.com');
  await page.locator('input[name="phone"]').fill('+14155551234');
  await page.locator('select[name*="how"]').selectOption({ index: 1 });
  await page.locator('textarea[name*="what"]').fill('Test notes');
  await page.getByRole('button', { name: /confirm/i }).click();

  await expect(
    page.getByText(/this meeting is scheduled/i)
  ).toBeVisible({ timeout: 15_000 });
});
50% fewer lines

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Timezone Selection and Date Boundary Edge Cases

Cal.com detects the booker's timezone automatically from the browser and displays time slots in that timezone. This creates a subtle but serious testing challenge: a slot that appears as “9:00 AM” in New York is “2:00 PM” in London and “11:00 PM” in Tokyo. If your CI server runs in UTC and your test expects a specific time label, the assertion will fail on every developer's machine in a different timezone.

Date boundary edge cases are even trickier. When a booker in Honolulu (UTC-10) looks at “tomorrow” at 11 PM, the host in Sydney (UTC+10) is already two days ahead. The available slots for what the booker considers “tomorrow” may map to a completely different calendar day on the host side. Your test needs to either pin the timezone explicitly in the Playwright config or use the Cal.com timezone selector to switch timezones during the test.

3

Timezone Selection and Date Boundaries

Complex

Playwright Implementation

calcom-timezone.spec.ts

What to Assert Beyond the UI

  • The booking stored in the Cal.com database has the correct UTC timestamp regardless of the display timezone
  • The confirmation email shows the time in the booker's selected timezone
  • Slots that cross the date boundary (11 PM in one timezone, 2 AM the next day in another) are correctly assigned to the right calendar date

6. Scenario: Embed Widget vs. Hosted Page

Many Cal.com users embed the booking widget inside their own application using the Cal.com embed script. The embed renders an iframe containing the full Cal.com booking experience inside the host page. From a Playwright perspective, the iframe creates a separate browsing context. Every locator you used on the hosted page must now be accessed through page.frameLocator() or page.frame(). Forgetting this is the single most common cause of Cal.com embed test failures.

Cal.com offers three embed modes: inline (rendered directly in the page), popup (triggered by a button click, opens a modal overlay), and floating popup (a persistent button in the corner). Each mode has different iframe injection timing. The inline embed loads with the page. The popup embed only creates the iframe when the trigger button is clicked. Your test must account for this timing difference.

4

Embed Widget: Inline and Popup Modes

Complex

Playwright Implementation

calcom-embed.spec.ts

Embed Booking: Playwright vs Assrt

test('inline embed booking', async ({ page }) => {
  await page.goto('/schedule');

  const calFrame = page.frameLocator(
    'iframe[src*="cal.com"]'
  );

  const day = calFrame.locator(
    '[data-testid="day"][disabled!=""]'
  ).first();
  await day.waitFor({ state: 'visible', timeout: 15_000 });
  await day.click();

  const slot = calFrame.locator('[data-testid="time"]').first();
  await slot.waitFor({ state: 'visible' });
  await slot.click();

  await calFrame.getByLabel(/your name/i).fill('Test');
  await calFrame.getByLabel(/email/i).fill('test@example.com');
  await calFrame.getByRole('button', { name: /confirm/i }).click();

  await expect(
    calFrame.getByText(/this meeting is scheduled/i)
  ).toBeVisible({ timeout: 15_000 });
});
50% fewer lines

7. Scenario: Recurring Event Booking

Cal.com supports recurring events where the booker selects a time slot and the system automatically creates multiple bookings at a defined interval (weekly, biweekly, monthly). The booking flow for recurring events differs from one-off bookings in several ways. The user sees a frequency selector and an occurrence count. After selecting a time slot, Cal.com displays a summary of all the dates that will be booked. The booker must confirm all occurrences at once.

Testing recurring bookings is tricky because availability must be open for every occurrence. If the host has a conflict on one of the recurring dates, Cal.com will either skip that date or show a warning. Your test needs to verify that the correct number of occurrences are created and that the confirmation page lists all of them.

5

Recurring Event Booking

Complex

Playwright Implementation

calcom-recurring.spec.ts

What to Assert Beyond the UI

  • The Cal.com API returns exactly N bookings sharing the same recurringEventId
  • Each occurrence is spaced at the correct interval (7 days for weekly)
  • Cancelling one occurrence does not cancel the entire series

8. Scenario: Post-Booking Redirects and Webhook Verification

Cal.com event types can be configured with a custom redirect URL that the booker is sent to after confirming a booking. Instead of seeing Cal.com's default confirmation page, the booker lands on your application's “thank you” page, a Typeform survey, or any external URL. This redirect changes the assertion target for your test: you can no longer look for Cal.com's “This meeting is scheduled” text.

Additionally, Cal.com supports webhooks that fire on booking creation, cancellation, and rescheduling. For a thorough end-to-end test, you should verify that your webhook endpoint received the correct payload. You can do this by setting up a local webhook receiver in your test infrastructure and checking it after the booking completes.

6

Post-Booking Redirect and Webhook

Moderate

Playwright Implementation

calcom-redirect.spec.ts

Redirect + Webhook: Playwright vs Assrt

test('post-booking redirect', async ({ page }) => {
  await page.goto('/e2e-test-user/sales-demo');

  const day = page.locator(
    '[data-testid="day"][disabled!=""]'
  ).first();
  await day.waitFor({ state: 'visible', timeout: 10_000 });
  await day.click();
  const slot = page.locator('[data-testid="time"]').first();
  await slot.waitFor({ state: 'visible' });
  await slot.click();

  await page.getByLabel(/your name/i).fill('Redirect Booker');
  await page.getByLabel(/email/i).fill('redirect@test.com');
  await page.getByRole('button', { name: /confirm/i }).click();

  await page.waitForURL(/\/thank-you/, { timeout: 20_000 });
  const url = new URL(page.url());
  expect(url.searchParams.has('bookingUid')).toBeTruthy();
  await expect(
    page.getByText(/thank you|booking received/i)
  ).toBeVisible();
});
53% fewer lines

9. Common Pitfalls That Break Cal.com Booking Tests

Stale Bookings Blocking Availability

The most common cause of flaky Cal.com tests is leftover bookings from previous runs. If your test books the 9:00 AM slot and the cleanup step fails or is skipped, the next run will not see that slot as available. The test then picks a different slot (or finds no slots at all if the day is fully booked). Always cancel all test bookings in your global setup using the Cal.com API before the suite starts. Filter by the test email pattern to avoid cancelling real bookings.

Race Condition on Time Slot Loading

Cal.com fetches available time slots via an API call after the user selects a date. If your test clicks a date and immediately tries to click a time slot, it will fail because the slots have not loaded yet. This is the single most reported Playwright issue with Cal.com on GitHub. Always use an explicit waitFor on the time slot element before interacting with it. Do not use a fixed waitForTimeout because slot loading time varies with network conditions and database size.

Embed iframe Not Found

When testing the Cal.com embed, the iframe may not exist in the DOM at the time your test looks for it. The popup embed only creates the iframe after the trigger button is clicked, and the inline embed may load asynchronously after the host page renders. Use page.frameLocator().locator().waitFor() to wait for content inside the iframe rather than checking for the iframe element itself. A common mistake is to assert the iframe tag exists (which may be true) but then immediately query inside it before Cal.com has loaded its content.

Timezone Drift Between CI and Local

If your test asserts on specific time slot text (like “10:00am”), it will pass on your machine but fail in CI because CI typically runs in UTC. Always set timezoneId in your Playwright config or browser context. If you need to test multiple timezones, create separate test contexts with explicit timezone settings rather than relying on the system timezone.

Dynamic Date Selectors Breaking on Weekends

If the host's availability is set to weekdays only and your test runs on a Saturday, there may be no available dates visible in the current calendar month view. Your test clicks the “first available day” but nothing matches. Handle this by checking if any available day exists in the current view and advancing the calendar month if needed. Alternatively, configure the test event type with 7-day availability to eliminate this edge case entirely.

Cal.com Test Anti-Patterns

  • Hardcoding specific time slot text instead of selecting the first available
  • Skipping global cleanup of test bookings between runs
  • Using page.locator() instead of frameLocator() for embed tests
  • Not setting timezoneId in Playwright config, causing CI failures
  • Using waitForTimeout instead of waitFor on async elements
  • Assuming weekday availability without checking current day
  • Testing against production Cal.com account with real bookings
  • Not verifying the booking via API after UI confirmation
Cal.com Booking Test Suite Run

10. Writing These Scenarios in Plain English with Assrt

Every scenario above involves selecting dates from a dynamic calendar, waiting for asynchronous slot loading, handling iframes for embeds, and managing per-event-type form variations. That is a lot of brittle selector logic. The recurring booking test alone is over 50 lines of Playwright code, and it breaks the moment Cal.com renames a data attribute or restructures its calendar component.

Assrt lets you describe what you want to test in plain English and handles the selector resolution at runtime. When Cal.com ships an update that changes the time slot data attributes from data-testid="time" to data-testid="time-slot", Assrt detects the failure, inspects the new DOM, and updates the generated locators automatically. Your scenario files stay unchanged.

Here is the embed booking scenario from Section 6 compiled into an Assrt file. Notice how the iframe boundary handling, the async waiting, and the selector specifics are all abstracted away.

scenarios/calcom-full-suite.assrt

Assrt compiles each scenario block into the equivalent Playwright TypeScript you saw in the preceding sections. The generated tests are committed to your repository as readable, runnable spec files. When Cal.com updates their booking page layout, renames form labels, or changes the embed iframe structure, Assrt detects the breakage, re-analyzes the page, and opens a pull request with updated locators. Your scenario definitions remain stable.

Start with the basic booking happy path. Once it passes in CI, add the custom fields scenario, then the timezone test, then the embed widget, then the recurring booking, then the redirect verification. Within a single afternoon you can have comprehensive Cal.com booking coverage that would take days to build and maintain by hand.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk