Skip to content

Commit 77b2be1

Browse files
fix: resolve flaky 'Book on column layout' E2E test (calcom#28682)
In column view, time slots are always visible. selectFirstAvailableTimeSlotNextMonth could click stale time slots from the current month before the schedule data refreshed for the new month. Since isQuickAvailabilityCheckFeatureEnabled is always true in E2E, isTimeSlotAvailable would check the stale slot against the new month's schedule data, find no match, and permanently disable the confirm button. Fix: wait for initial schedule data to load before setting up a waitForResponse listener for getSchedule, then click incrementMonth and await the response before selecting slots. Ported from calcom/cal#1107.
1 parent 78ddc1b commit 77b2be1

2 files changed

Lines changed: 32 additions & 10 deletions

File tree

apps/web/playwright/booking-pages.e2e.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1+
import { WEBAPP_URL } from "@calcom/lib/constants";
2+
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
3+
import { randomString } from "@calcom/lib/random";
4+
import { SchedulingType } from "@calcom/prisma/enums";
5+
import type { Schedule, TimeRange } from "@calcom/types/schedule";
6+
import { expect } from "@playwright/test";
7+
import { JSDOM } from "jsdom";
18
import { test, todo } from "./lib/fixtures";
29
import {
310
bookFirstEvent,
411
bookOptinEvent,
512
bookTimeSlot,
13+
cancelBookingFromBookingsList,
614
confirmBooking,
715
confirmReschedule,
816
expectSlotNotAllowedToBook,
917
selectFirstAvailableTimeSlotNextMonth,
1018
testEmail,
1119
testName,
12-
cancelBookingFromBookingsList,
1320
} from "./lib/testUtils";
14-
import { WEBAPP_URL } from "@calcom/lib/constants";
15-
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
16-
import { randomString } from "@calcom/lib/random";
17-
import { SchedulingType } from "@calcom/prisma/enums";
18-
import type { Schedule, TimeRange } from "@calcom/types/schedule";
19-
import { expect } from "@playwright/test";
20-
import { JSDOM } from "jsdom";
2121

2222
const freeUserObj = { name: `Free-user-${randomString(3)}` };
2323
test.describe.configure({ mode: "parallel" });
@@ -486,8 +486,6 @@ test.describe("Booking on different layouts", () => {
486486

487487
await page.click('[data-testid="toggle-group-item-column_view"]');
488488

489-
// Use the standard helper to select an available time slot next month
490-
// This is more robust than manually clicking incrementMonth and reloading
491489
await selectFirstAvailableTimeSlotNextMonth(page);
492490

493491
// Fill what is this meeting about? name email and notes

apps/web/playwright/lib/testUtils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,22 @@ export async function selectFirstAvailableTimeSlotNextMonth(page: Page | Frame)
109109
// Wait for the booker to be ready before interacting
110110
const incrementMonth = page.getByTestId("incrementMonth");
111111
await incrementMonth.waitFor();
112+
113+
// Wait for the initial schedule data to load (available days rendered in date picker).
114+
// This ensures the waitForResponse listener below only catches the month-change response,
115+
// not the initial page load response. Without this, column view can race: stale time slots
116+
// from the current month get clicked before the new month's schedule data arrives, causing
117+
// the quick availability check to permanently disable the confirm button.
118+
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor();
119+
120+
// Listen for the getSchedule response triggered by the month change.
121+
// waitForResponse is only available on Page, not Frame.
122+
const scheduleResponse =
123+
"waitForResponse" in page
124+
? page.waitForResponse((resp) => resp.url().includes("getSchedule") && resp.status() === 200)
125+
: Promise.resolve();
112126
await incrementMonth.click();
127+
await scheduleResponse;
113128

114129
// Wait for available day to appear after month increment
115130
const firstAvailableDay = page.locator('[data-testid="day"][data-disabled="false"]').nth(0);
@@ -125,7 +140,16 @@ export async function selectSecondAvailableTimeSlotNextMonth(page: Page) {
125140
// Wait for the booker to be ready before interacting
126141
const incrementMonth = page.getByTestId("incrementMonth");
127142
await incrementMonth.waitFor();
143+
144+
// Wait for initial schedule data to load before changing month (see selectFirstAvailableTimeSlotNextMonth)
145+
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor();
146+
147+
// Listen for the getSchedule response triggered by the month change
148+
const scheduleResponse = page.waitForResponse(
149+
(resp) => resp.url().includes("getSchedule") && resp.status() === 200
150+
);
128151
await incrementMonth.click();
152+
await scheduleResponse;
129153

130154
// Wait for available day to appear after month increment
131155
const secondAvailableDay = page.locator('[data-testid="day"][data-disabled="false"]').nth(1);

0 commit comments

Comments
 (0)