Files
calendar/apps/web/playwright/fixtures/embeds.ts
T
Keith WilliamsGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
4226d2262d 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>
2025-12-29 18:35:50 +05:30

156 lines
6.4 KiB
TypeScript

import type { Page } from "@playwright/test";
export const createEmbedsFixture = (page: Page) => {
return {
/**
* @deprecated
* Use 'gotoPlayground' instead, to navigate. It calls `addEmbedListeners` automatically.
*/
async addEmbedListeners(calNamespace: string) {
await page.addInitScript(
({ calNamespace }: { calNamespace: string }) => {
console.log(
"PlaywrightTest - InitScript:",
"Adding listener for __iframeReady on namespace:",
calNamespace
);
// 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.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyVisibility = document.body.style.visibility;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyBackground = document.body.style.background;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialValuesSet = true;
return;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
let api = window.Cal;
if (!api) {
console.log("PlaywrightTest:", "window.Cal not available yet, trying again");
setTimeout(tryAddingListener, 500);
return;
}
if (calNamespace) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
api = window.Cal.ns[calNamespace];
console.log("Using api from namespace-", { calNamespace, api });
}
if (!api) {
console.log(`namespace "${calNamespace}" not found yet - Trying again`);
setTimeout(tryAddingListener, 500);
return;
}
console.log("PlaywrightTest:", `Adding listener for __iframeReady on namespace:${calNamespace}`);
api("on", {
action: "*",
callback: (e) => {
console.log("Playwright Embed Fixture: Received event", JSON.stringify(e.detail));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.iframeReady = true; // Technically if there are multiple cal embeds, it can be set due to some other iframe. But it works for now. Improve it when it doesn't work
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const store = window.eventsFiredStoreForPlaywright;
const eventStore = (store[`${e.detail.type}-${e.detail.namespace}`] =
store[`${e.detail.type}-${e.detail.namespace}`] || []);
eventStore.push(e.detail);
},
});
});
},
{ calNamespace }
);
page.on("console", (msg) => {
console.log(`Browser Console: ${msg.type()}: ${msg.text()}`);
});
page.on("framenavigated", async (frame) => {
console.log(`Navigation occurred in frame: ${frame.url()}`);
});
page.on("pageerror", (error) => {
console.error(`Page error: ${error.message}`);
});
page.on("requestfailed", (request) => {
console.error(`Failed request: ${request.url()}, ${request.failure()?.errorText}`);
});
},
async getActionFiredDetails({ calNamespace, actionType }: { calNamespace: string; actionType: string }) {
if (!page.isClosed()) {
return await page.evaluate(
({ actionType, calNamespace }) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`];
},
{ actionType, calNamespace }
);
}
},
async gotoPlayground({ calNamespace, url }: { calNamespace: string; url: string }) {
await this.addEmbedListeners(calNamespace);
await page.goto(url);
},
};
};