fix: add postMessage listener to capture __iframeReady events early (#26258)

This fixes a race condition in the embed E2E tests where the __iframeReady
event could fire before the Cal.ns[namespace] API was ready to receive it.

The fix adds a window.message listener immediately in the addInitScript that
captures __iframeReady events directly from postMessage, which doesn't depend
on the namespace API being ready. This ensures window.iframeReady is set
even if the event fires before the Cal API listener is attached.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Keith Williams
2025-12-29 18:35:50 +05:30
committed by GitHub
co-authored by Devin AI
parent 00c7415e28
commit 4226d2262d
4 changed files with 69 additions and 13 deletions
+4 -4
View File
@@ -114,7 +114,7 @@ export async function createFilterSegment(
* Select a segment from the dropdown
*/
export async function selectSegment(page: Page, segmentName: string) {
await page.getByTestId("filter-segment-select").click();
await page.getByTestId("filter-segment-select").first().click();
await page
.locator('[data-testid="filter-segment-select-content"] [role="menuitem"]')
@@ -126,7 +126,7 @@ export async function selectSegment(page: Page, segmentName: string) {
* Open submenu of a certain segment
*/
export async function openSegmentSubmenu(page: Page, segmentName: string) {
await page.getByTestId("filter-segment-select").click();
await page.getByTestId("filter-segment-select").first().click();
await page
.locator('[data-testid="filter-segment-select-content"] [role="menuitem"]')
@@ -183,7 +183,7 @@ export function locateSelectedSegmentName(page: Page, expectedName: string) {
* Check if a system segment is visible in the dropdown
*/
export async function expectSystemSegmentVisible(page: Page, segmentName: string) {
await page.getByTestId("filter-segment-select").click();
await page.getByTestId("filter-segment-select").first().click();
await expect(
page.locator('[data-testid="filter-segment-select-content"]').getByText("Default")
).toBeVisible();
@@ -240,7 +240,7 @@ export async function renameSegment(page: Page, originalName: string, newName: s
* Check if a segment group (like "Default" or "Personal") is visible
*/
export async function expectSegmentGroupVisible(page: Page, groupName: string) {
await page.getByTestId("filter-segment-select").click();
await page.getByTestId("filter-segment-select").first().click();
await expect(
page.locator('[data-testid="filter-segment-select-content"]').getByText(groupName)
).toBeVisible();
+40
View File
@@ -17,6 +17,46 @@ export const createEmbedsFixture = (page: Page) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.eventsFiredStoreForPlaywright = window.eventsFiredStoreForPlaywright || {};
// Add a postMessage listener immediately to capture __iframeReady events
// This avoids the race condition where the event fires before Cal.ns[namespace] is ready
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
if (!window.postMessageListenerAdded) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.postMessageListenerAdded = true;
window.addEventListener("message", (event) => {
// Filter for Cal.com embed messages
if (
event.data &&
typeof event.data === "object" &&
event.data.originator === "CAL" &&
event.data.type
) {
const { type, namespace, data } = event.data;
console.log("PlaywrightTest postMessage:", `Received ${type} for namespace ${namespace}`);
// Set iframeReady when we receive __iframeReady event for our namespace
if (type === "__iframeReady" && namespace === calNamespace) {
console.log("PlaywrightTest postMessage:", "Setting window.iframeReady = true");
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.iframeReady = true;
}
// Store the event in our event store
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const store = window.eventsFiredStoreForPlaywright;
if (store) {
const eventStore = (store[`${type}-${namespace}`] = store[`${type}-${namespace}`] || []);
eventStore.push({ type, namespace, fullType: `CAL:${namespace}:${type}`, data });
}
}
});
}
document.addEventListener("DOMContentLoaded", function tryAddingListener() {
if (parent !== window) {
// Firefox seems to execute this snippet for iframe as well. Avoid that. It must be executed only for parent frame.
+1 -1
View File
@@ -46,7 +46,7 @@ test.describe("System Segments", () => {
await navigateToBookings(page);
await page.getByTestId("filter-segment-select").click();
await page.getByTestId("filter-segment-select").first().click();
await page
.locator('[data-testid="filter-segment-select-content"] [role="menuitem"]')
.filter({ hasText: "My Bookings" })
@@ -33,7 +33,10 @@ test.describe("Team", () => {
await page.waitForTimeout(500); // Add a small delay to ensure UI is fully loaded
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.getByText(t("send_invite")).click();
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
const inviteLink = await expectInvitationEmailToBeReceived(
page,
emails,
@@ -119,7 +122,10 @@ test.describe("Team", () => {
await page.waitForTimeout(500); // Add a small delay to ensure UI is fully loaded
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.getByText(t("send_invite")).click();
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
await expectInvitationEmailToBeReceived(
page,
emails,
@@ -167,13 +173,17 @@ test.describe("Team", () => {
await page.waitForTimeout(500); // Add a small delay to ensure UI is fully loaded
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedMember.email);
await page.getByText(t("send_invite")).click();
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
await invitedMember.apiLogin();
await page.goto(`/teams`);
await page.getByTestId(`accept-invitation-${team.id}`).click();
const response = await page.waitForResponse("/api/trpc/teams/acceptOrLeave?batch=1");
expect(response.status()).toBe(200);
const response2Promise = page.waitForResponse("/api/trpc/teams/acceptOrLeave?batch=1");
await page.getByTestId(`accept-invitation-${team.id}`).first().click();
const response2 = await response2Promise;
expect(response2.status()).toBe(200);
await page.goto(`/event-types`);
//ensure managed event-type is created for the invited member
@@ -211,7 +221,10 @@ test.describe("Team", () => {
await page.waitForTimeout(500);
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUser.email);
await page.getByText(t("send_invite")).click();
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
inviteLink = await expectInvitationEmailToBeReceived(
page,
@@ -275,7 +288,10 @@ test.describe("Team", () => {
await page.waitForTimeout(500);
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUser.email);
await page.getByText(t("send_invite")).click();
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
inviteLink = await expectInvitationEmailToBeReceived(
page,