first commit
This commit is contained in:
167
calcom/packages/embeds/embed-core/playwright/lib/testUtils.ts
Normal file
167
calcom/packages/embeds/embed-core/playwright/lib/testUtils.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { Page, Frame } from "@playwright/test";
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export function todo(title: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, playwright/no-skipped-test
|
||||
test.skip(title, () => {});
|
||||
}
|
||||
|
||||
export const deleteAllBookingsByEmail = async (email: string) =>
|
||||
await prisma.booking.deleteMany({
|
||||
where: {
|
||||
attendees: {
|
||||
some: {
|
||||
email: email,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const getBooking = async (bookingId: string) => {
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: bookingId,
|
||||
},
|
||||
include: {
|
||||
attendees: true,
|
||||
},
|
||||
});
|
||||
if (!booking) {
|
||||
throw new Error("Booking not found");
|
||||
}
|
||||
return booking;
|
||||
};
|
||||
|
||||
export const getEmbedIframe = async ({
|
||||
calNamespace,
|
||||
page,
|
||||
pathname,
|
||||
}: {
|
||||
calNamespace: string;
|
||||
page: Page;
|
||||
pathname: string;
|
||||
}) => {
|
||||
// We can't seem to access page.frame till contentWindow is available. So wait for that.
|
||||
const iframeReady = await page.evaluate(
|
||||
(hardTimeout) => {
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const iframe = document.querySelector<HTMLIFrameElement>(".cal-embed");
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (iframe && iframe.contentWindow && window.iframeReady) {
|
||||
clearInterval(interval);
|
||||
resolve(true);
|
||||
} else {
|
||||
console.log("Waiting for all three to be true:", {
|
||||
iframeElement: iframe,
|
||||
contentWindow: iframe?.contentWindow,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
iframeReady: window.iframeReady,
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// A hard timeout if iframe isn't ready in that time. Avoids infinite wait
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
resolve(false);
|
||||
// This is the time embed-iframe.ts loads in the iframe and fires atleast one event. Also, it is a load of entire React Application so it can sometime take more time even on CI.
|
||||
}, hardTimeout);
|
||||
});
|
||||
},
|
||||
!process.env.CI ? 150000 : 15000
|
||||
);
|
||||
if (!iframeReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We just verified that iframeReady is true here, so obviously embedIframe is not null
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const embedIframe = page.frame(`cal-embed=${calNamespace}`)!;
|
||||
const u = new URL(embedIframe.url());
|
||||
if (u.pathname === `${pathname}/embed`) {
|
||||
return embedIframe;
|
||||
}
|
||||
console.log(`Embed iframe url pathname match. Expected: "${pathname}/embed"`, `Actual: ${u.pathname}`);
|
||||
return null;
|
||||
};
|
||||
|
||||
async function selectFirstAvailableTimeSlotNextMonth(frame: Frame, page: Page) {
|
||||
await frame.click('[data-testid="incrementMonth"]');
|
||||
|
||||
// @TODO: Find a better way to make test wait for full month change render to end
|
||||
// so it can click up on the right day, also when done, resolve other todos as well
|
||||
// The problem is that the Month Text changes instantly but we don't know when the corresponding dates are visible
|
||||
|
||||
// Waiting for full month increment
|
||||
await frame.waitForTimeout(1000);
|
||||
// expect(await page.screenshot()).toMatchSnapshot("availability-page-2.png");
|
||||
// TODO: Find out why the first day is always booked on tests
|
||||
await frame.locator('[data-testid="day"][data-disabled="false"]').nth(1).click();
|
||||
await frame.click('[data-testid="time"]');
|
||||
}
|
||||
|
||||
export async function bookFirstEvent(username: string, frame: Frame, page: Page) {
|
||||
// Click first event type on Profile Page
|
||||
await frame.click('[data-testid="event-type-link"]');
|
||||
await frame.waitForURL((url) => {
|
||||
// Wait for reaching the event page
|
||||
const matches = url.pathname.match(new RegExp(`/${username}/(.+)$`));
|
||||
if (!matches || !matches[1]) {
|
||||
return false;
|
||||
}
|
||||
if (matches[1] === "embed") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Let current month dates fully render.
|
||||
// There is a bug where if we don't let current month fully render and quickly click go to next month, current month get's rendered
|
||||
// This doesn't seem to be replicable with the speed of a person, only during automation.
|
||||
// It would also allow correct snapshot to be taken for current month.
|
||||
await frame.waitForTimeout(1000);
|
||||
// expect(await page.screenshot()).toMatchSnapshot("availability-page-1.png");
|
||||
// Remove /embed from the end if present.
|
||||
const eventSlug = new URL(frame.url()).pathname.replace(/\/embed$/, "");
|
||||
await selectFirstAvailableTimeSlotNextMonth(frame, page);
|
||||
// expect(await page.screenshot()).toMatchSnapshot("booking-page.png");
|
||||
// --- fill form
|
||||
await frame.fill('[name="name"]', "Embed User");
|
||||
await frame.fill('[name="email"]', "embed-user@example.com");
|
||||
await frame.press('[name="email"]', "Enter");
|
||||
const response = await page.waitForResponse("**/api/book/event");
|
||||
const booking = (await response.json()) as { uid: string; eventSlug: string };
|
||||
booking.eventSlug = eventSlug;
|
||||
|
||||
// Make sure we're navigated to the success page
|
||||
await expect(frame.locator("[data-testid=success-page]")).toBeVisible();
|
||||
// expect(await page.screenshot()).toMatchSnapshot("success-page.png");
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
export async function rescheduleEvent(username: string, frame: Frame, page: Page) {
|
||||
await selectFirstAvailableTimeSlotNextMonth(frame, page);
|
||||
// --- fill form
|
||||
await frame.press('[name="email"]', "Enter");
|
||||
await frame.click("[data-testid=confirm-reschedule-button]");
|
||||
const response = await page.waitForResponse("**/api/book/event");
|
||||
const responseObj = await response.json();
|
||||
const booking = responseObj.uid;
|
||||
// Make sure we're navigated to the success page
|
||||
await expect(frame.locator("[data-testid=success-page]")).toBeVisible();
|
||||
return booking;
|
||||
}
|
||||
|
||||
export async function installAppleCalendar(page: Page) {
|
||||
await page.goto("/apps/categories/calendar");
|
||||
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
|
||||
await page.waitForURL("/apps/apple-calendar");
|
||||
await page.click('[data-testid="install-app-button"]');
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
import type { Fixtures } from "@calcom/web/playwright/lib/fixtures";
|
||||
import { selectFirstAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils";
|
||||
|
||||
import {
|
||||
todo,
|
||||
getEmbedIframe,
|
||||
bookFirstEvent,
|
||||
getBooking,
|
||||
deleteAllBookingsByEmail,
|
||||
rescheduleEvent,
|
||||
} from "../lib/testUtils";
|
||||
|
||||
// in parallel mode sometimes handleNewBooking endpoint throws "No available users found" error, this never happens in serial mode.
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
async function bookFirstFreeUserEventThroughEmbed({
|
||||
addEmbedListeners,
|
||||
page,
|
||||
getActionFiredDetails,
|
||||
}: {
|
||||
addEmbedListeners: Fixtures["embeds"]["addEmbedListeners"];
|
||||
page: Page;
|
||||
getActionFiredDetails: Fixtures["embeds"]["getActionFiredDetails"];
|
||||
}) {
|
||||
const embedButtonLocator = page.locator('[data-cal-link="free"]').first();
|
||||
await page.goto("/");
|
||||
// Obtain cal namespace from the element being clicked itself, so that addEmbedListeners always listen to correct namespace
|
||||
const calNamespace = (await embedButtonLocator.getAttribute("data-cal-namespace")) || "";
|
||||
await addEmbedListeners(calNamespace);
|
||||
// Goto / again so that initScript attached using addEmbedListeners can work now.
|
||||
await page.goto("/");
|
||||
|
||||
await embedButtonLocator.click();
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/free" });
|
||||
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/free",
|
||||
});
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
const booking = await bookFirstEvent("free", embedIframe, page);
|
||||
return booking;
|
||||
}
|
||||
|
||||
//TODO: Change these tests to use a user/eventType per embed type atleast. This is so that we can test different themes,layouts configured in App or per EventType
|
||||
test.describe("Popup Tests", () => {
|
||||
test.afterEach(async () => {
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
});
|
||||
|
||||
test("should open embed iframe on click - Configured with light theme", async ({ page, embeds }) => {
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
const calNamespace = "e2ePopupLightTheme";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/" });
|
||||
|
||||
await page.click(`[data-cal-namespace="${calNamespace}"]`);
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/free" });
|
||||
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: "/free",
|
||||
});
|
||||
// expect(await page.screenshot()).toMatchSnapshot("event-types-list.png");
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
const { uid: bookingId } = await bookFirstEvent("free", embedIframe, page);
|
||||
const booking = await getBooking(bookingId);
|
||||
|
||||
expect(booking.attendees.length).toBe(1);
|
||||
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
});
|
||||
|
||||
test("should be able to reschedule", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
const booking = await test.step("Create a booking", async () => {
|
||||
return await bookFirstFreeUserEventThroughEmbed({
|
||||
page,
|
||||
addEmbedListeners,
|
||||
getActionFiredDetails,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("Reschedule the booking", async () => {
|
||||
await addEmbedListeners("popupReschedule");
|
||||
await page.goto(`/?popupRescheduleId=${booking.uid}`);
|
||||
await page.click('[data-cal-namespace="popupReschedule"]');
|
||||
const calNamespace = "popupReschedule";
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: booking.eventSlug });
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
await rescheduleEvent("free", embedIframe, page);
|
||||
});
|
||||
});
|
||||
|
||||
todo("Add snapshot test for embed iframe");
|
||||
|
||||
test("should open Routing Forms embed on click", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
|
||||
const calNamespace = "routingFormAuto";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=prerender-test");
|
||||
let embedIframe = await getEmbedIframe({
|
||||
calNamespace,
|
||||
page,
|
||||
pathname: "/forms/948ae412-d995-4865-875a-48302588de03",
|
||||
});
|
||||
expect(embedIframe).toBeFalsy();
|
||||
await page.click(
|
||||
`[data-cal-namespace=${calNamespace}][data-cal-link="forms/948ae412-d995-4865-875a-48302588de03"]`
|
||||
);
|
||||
embedIframe = await getEmbedIframe({
|
||||
calNamespace,
|
||||
page,
|
||||
pathname: "/forms/948ae412-d995-4865-875a-48302588de03",
|
||||
});
|
||||
if (!embedIframe) {
|
||||
throw new Error("Routing Form embed iframe not found");
|
||||
}
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/forms/948ae412-d995-4865-875a-48302588de03",
|
||||
});
|
||||
await expect(embedIframe.locator("text=Seeded Form - Pro")).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("Floating Button Popup", () => {
|
||||
test.describe("Pro User - Configured in App with default setting of system theme", () => {
|
||||
test("should open embed iframe according to system theme when no theme is configured through Embed API", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
const calNamespace = "floatingButton";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=ns:floatingButton");
|
||||
|
||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
});
|
||||
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
const html = embedIframe.locator("html");
|
||||
// Expect "light" theme as configured in App for pro user.
|
||||
await expect(html).toHaveAttribute("class", "light");
|
||||
const { uid: bookingId } = await bookFirstEvent("pro", embedIframe, page);
|
||||
const booking = await getBooking(bookingId);
|
||||
expect(booking.attendees.length).toBe(3);
|
||||
await test.step("Close the modal", async () => {
|
||||
await page.locator("cal-modal-box .close").click();
|
||||
await expect(page.locator("cal-modal-box")).toBeHidden();
|
||||
await expect(page.locator("cal-modal-box iframe")).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test("should open embed iframe according to system theme when configured with 'auto' theme using Embed API", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
const calNamespace = "floatingButton";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=ns:floatingButton");
|
||||
|
||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
});
|
||||
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
|
||||
const html = embedIframe.locator("html");
|
||||
const prefersDarkScheme = await page.evaluate(() => {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
});
|
||||
// Detect browser preference and expect accordingly
|
||||
await expect(html).toHaveAttribute("class", prefersDarkScheme ? "dark" : "light");
|
||||
});
|
||||
|
||||
test("should open embed iframe(Booker Profile Page) with dark theme when configured with dark theme using Embed API", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
const calNamespace = "floatingButton";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=ns:floatingButton&theme=dark");
|
||||
|
||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
});
|
||||
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
|
||||
const html = embedIframe.locator("html");
|
||||
await expect(html).toHaveAttribute("class", "dark");
|
||||
});
|
||||
|
||||
test("should open embed iframe(Event Booking Page) with dark theme when configured with dark theme using Embed API", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
const calNamespace = "floatingButton";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=ns:floatingButton&cal-link=pro/30min&theme=dark");
|
||||
|
||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro/30min" });
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/pro/30min",
|
||||
});
|
||||
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
|
||||
const html = embedIframe.locator("html");
|
||||
await expect(html).toHaveAttribute("class", "dark");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("prendered embed should be loaded and apply the config given to it", async ({ page, embeds }) => {
|
||||
const calNamespace = "e2ePrerenderLightTheme";
|
||||
const calLink = "/free/30min";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/?only=prerender-test" });
|
||||
await expectPrerenderedIframe({ calNamespace, calLink, embeds, page });
|
||||
|
||||
await page.click(`[data-cal-namespace="${calNamespace}"]`);
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: calLink });
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
await selectFirstAvailableTimeSlotNextMonth(embedIframe);
|
||||
await expect(embedIframe.locator('[name="name"]')).toHaveValue("Preloaded Prefilled");
|
||||
await expect(embedIframe.locator('[name="email"]')).toHaveValue("preloaded-prefilled@example.com");
|
||||
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: calLink,
|
||||
});
|
||||
});
|
||||
|
||||
test("should open on clicking child element", async ({ page, embeds }) => {
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
const calNamespace = "childElementTarget";
|
||||
const configuredLink = "/free/30min";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/" });
|
||||
|
||||
await page.click(`[data-cal-namespace="${calNamespace}"] b`);
|
||||
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: configuredLink });
|
||||
|
||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: configuredLink,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function expectPrerenderedIframe({
|
||||
page,
|
||||
calNamespace,
|
||||
calLink,
|
||||
embeds,
|
||||
}: {
|
||||
page: Page;
|
||||
calNamespace: string;
|
||||
calLink: string;
|
||||
embeds: Fixtures["embeds"];
|
||||
}) {
|
||||
const prerenderedIframe = await getEmbedIframe({ calNamespace, page, pathname: calLink });
|
||||
|
||||
if (!prerenderedIframe) {
|
||||
throw new Error("Prerendered iframe not found");
|
||||
}
|
||||
await expect(prerenderedIframe).toBeEmbedCalLink(
|
||||
calNamespace,
|
||||
embeds.getActionFiredDetails,
|
||||
{
|
||||
pathname: calLink,
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
import "../../src/types";
|
||||
|
||||
test.describe("Embed Pages", () => {
|
||||
test("Event Type Page: should not have margin top on embed page", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/free/30min/embed");
|
||||
// Checks the margin from top by checking the distance between the div inside main from the viewport
|
||||
const marginFromTop = await page.evaluate(async () => {
|
||||
return await new Promise<{
|
||||
bookerContainer: number;
|
||||
mainEl: number;
|
||||
}>((resolve) => {
|
||||
(function tryGettingBoundingRect() {
|
||||
const mainElement = document.querySelector(".main");
|
||||
const bookerContainer = document.querySelector('[data-testid="booker-container"]');
|
||||
|
||||
if (mainElement && bookerContainer) {
|
||||
// This returns the distance of the div element from the viewport
|
||||
const mainElBoundingRect = mainElement.getBoundingClientRect();
|
||||
const bookerContainerBoundingRect = bookerContainer.getBoundingClientRect();
|
||||
resolve({ bookerContainer: bookerContainerBoundingRect.top, mainEl: mainElBoundingRect.top });
|
||||
} else {
|
||||
setTimeout(tryGettingBoundingRect, 500);
|
||||
}
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
expect(marginFromTop.bookerContainer).toBe(0);
|
||||
expect(marginFromTop.mainEl).toBe(0);
|
||||
});
|
||||
|
||||
test("Event Type Page: should have margin top on non embed page", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/free/30min");
|
||||
|
||||
// Checks the margin from top by checking the distance between the div inside main from the viewport
|
||||
const marginFromTop = await page.evaluate(() => {
|
||||
const mainElement = document.querySelector("main");
|
||||
const divElement = mainElement?.querySelector("div");
|
||||
|
||||
if (mainElement && divElement) {
|
||||
// This returns the distance of the div element from the viewport
|
||||
const divRect = divElement.getBoundingClientRect();
|
||||
return divRect.top;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
expect(marginFromTop).not.toBe(0);
|
||||
});
|
||||
|
||||
test.describe("isEmbed, getEmbedNamespace, getEmbedTheme testing", () => {
|
||||
test("when `window.name` is set to 'cal-embed=' and `theme` is supplied as a query param", async ({
|
||||
page,
|
||||
}) => {
|
||||
const queryParamTheme = "dark";
|
||||
await page.evaluate(() => {
|
||||
window.name = "cal-embed=";
|
||||
});
|
||||
|
||||
await page.goto(`http://localhost:3000/free/30min?theme=${queryParamTheme}`);
|
||||
|
||||
const isEmbed = await page.evaluate(() => {
|
||||
return window?.isEmbed?.();
|
||||
});
|
||||
|
||||
const embedNamespace = await page.evaluate(() => {
|
||||
return window?.getEmbedNamespace?.();
|
||||
});
|
||||
|
||||
expect(embedNamespace).toBe("");
|
||||
expect(isEmbed).toBe(true);
|
||||
const embedTheme = await page.evaluate(() => {
|
||||
return window?.getEmbedTheme?.();
|
||||
});
|
||||
expect(embedTheme).toBe(queryParamTheme);
|
||||
const embedStoreTheme = await page.evaluate(() => {
|
||||
return window.CalEmbed.embedStore.theme;
|
||||
});
|
||||
// Verify that the theme is set on embedStore.
|
||||
expect(embedStoreTheme).toBe(queryParamTheme);
|
||||
});
|
||||
|
||||
test("when `window.name` does not contain `cal-embed=`", async ({ page }) => {
|
||||
await page.evaluate(() => {
|
||||
window.name = "testing";
|
||||
});
|
||||
await page.goto(`http://localhost:3000/free/30min`);
|
||||
const isEmbed = await page.evaluate(() => {
|
||||
return window?.isEmbed?.();
|
||||
});
|
||||
const embedNamespace = await page.evaluate(() => {
|
||||
return window?.getEmbedNamespace?.();
|
||||
});
|
||||
expect(isEmbed).toBe(false);
|
||||
expect(embedNamespace).toBe(null);
|
||||
});
|
||||
|
||||
test("`getEmbedTheme` should use `window.CalEmbed.embedStore.theme` instead of `theme` query param if set", async ({
|
||||
page,
|
||||
}) => {
|
||||
const theme = "dark";
|
||||
await page.evaluate(() => {
|
||||
window.name = "cal-embed=";
|
||||
});
|
||||
await page.goto("http://localhost:3000/free/30min?theme=dark");
|
||||
let embedTheme = await page.evaluate(() => {
|
||||
return window?.getEmbedTheme?.();
|
||||
});
|
||||
expect(embedTheme).toBe(theme);
|
||||
|
||||
// Fake a scenario where theme query param is lost during navigation
|
||||
await page.evaluate(() => {
|
||||
history.pushState({}, "", "/free/30min");
|
||||
});
|
||||
|
||||
embedTheme = await page.evaluate(() => {
|
||||
return window?.getEmbedTheme?.();
|
||||
});
|
||||
|
||||
// Theme should still remain same as it's read from `window.CalEmbed.embedStore.theme` which is updated by getEmbedTheme itself
|
||||
expect(embedTheme).toBe(theme);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
import { bookFirstEvent, deleteAllBookingsByEmail, getEmbedIframe, todo } from "../lib/testUtils";
|
||||
|
||||
test.describe("Inline Iframe", () => {
|
||||
test("Inline Iframe - Configured with Dark Theme", async ({
|
||||
page,
|
||||
embeds: { addEmbedListeners, getActionFiredDetails },
|
||||
}) => {
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
await addEmbedListeners("");
|
||||
await page.goto("/?only=ns:default");
|
||||
const calNamespace = "";
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
theme: "dark",
|
||||
},
|
||||
});
|
||||
// expect(await page.screenshot()).toMatchSnapshot("event-types-list.png");
|
||||
if (!embedIframe) {
|
||||
throw new Error("Embed iframe not found");
|
||||
}
|
||||
await bookFirstEvent("pro", embedIframe, page);
|
||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||
});
|
||||
|
||||
todo(
|
||||
"Ensure that on all pages - [user], [user]/[type], team/[slug], team/[slug]/book, UI styling works if these pages are directly linked in embed"
|
||||
);
|
||||
|
||||
todo("Check that UI Configuration doesn't work for Free Plan");
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
import { getEmbedIframe } from "../lib/testUtils";
|
||||
|
||||
test.describe("Namespacing", () => {
|
||||
test.describe("Inline Embed", () => {
|
||||
test("Add inline embed using a namespace without reload", async ({ page, embeds }) => {
|
||||
const calNamespace = "withoutReloadNamespace";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/" });
|
||||
await page.click("#add-inline-embed-in-a-new-namespace-without-reload-button");
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
case: "addInlineEmbedInANewNamespaceWithoutReload",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("Double install Embed Snippet with inline embed using a namespace", async ({ page, embeds }) => {
|
||||
const calNamespace = "doubleInstall";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/" });
|
||||
await page.click("#double-install-snippet-with-inline-embed-non-default-namespace-button");
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
case: "doubleInstallSnippetWithInlineEmbedWithNonDefaultNamespace",
|
||||
},
|
||||
});
|
||||
expect(await page.locator("iframe").count()).toBe(1);
|
||||
});
|
||||
|
||||
test("Double install Embed Snippet with inline embed without a namespace(i.e. default namespace)", async ({
|
||||
page,
|
||||
embeds,
|
||||
}) => {
|
||||
const calNamespace = "";
|
||||
await embeds.gotoPlayground({ calNamespace, url: "/" });
|
||||
await page.click("#double-install-snippet-with-inline-embed-default-namespace-button");
|
||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
case: "doubleInstallSnippetWithInlineEmbed",
|
||||
},
|
||||
});
|
||||
expect(await page.locator("iframe").count()).toBe(1);
|
||||
});
|
||||
});
|
||||
test("Different namespaces can have different init configs", async ({ page, embeds }) => {
|
||||
await Promise.all([
|
||||
embeds.addEmbedListeners("namespace-init-test-1"),
|
||||
embeds.addEmbedListeners("namespace-init-test-2"),
|
||||
]);
|
||||
|
||||
await page.goto("/");
|
||||
await page.click("#two-different-namespace-with-different-init-config");
|
||||
const namespace1IframeSrc = await page.locator("iframe").nth(0).getAttribute("src");
|
||||
const namespace2IframeSrc = await page.locator("iframe").nth(1).getAttribute("src");
|
||||
expect(namespace1IframeSrc).toContain("http://localhost:3000/pro");
|
||||
expect(namespace2IframeSrc).toContain("http://127.0.0.1:3000/pro");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
test.describe("Preview", () => {
|
||||
test("Preview - embed-core should load if correct embedLibUrl is provided", async ({ page }) => {
|
||||
await page.goto(
|
||||
"http://localhost:3000/embed/preview.html?embedLibUrl=http://localhost:3000/embed/embed.js&bookerUrl=http://localhost:3000&calLink=pro/30min"
|
||||
);
|
||||
const libraryLoaded = await page.evaluate(() => {
|
||||
return new Promise((resolve) => {
|
||||
setInterval(() => {
|
||||
if (
|
||||
(
|
||||
window as unknown as {
|
||||
Cal: {
|
||||
__css: string;
|
||||
};
|
||||
}
|
||||
).Cal.__css
|
||||
) {
|
||||
resolve(true);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
expect(libraryLoaded).toBe(true);
|
||||
});
|
||||
|
||||
test("Preview - embed-core should load from embedLibUrl", async ({ page }) => {
|
||||
// Intentionally pass a URL that will not load to be able to easily test that the embed was loaded from there
|
||||
page.goto(
|
||||
"http://localhost:3000/embed/preview.html?embedLibUrl=http://wronglocalhost:3000/embed/embed.js&bookerUrl=http://localhost:3000&calLink=pro/30min"
|
||||
);
|
||||
|
||||
const failedRequestUrl = await new Promise<string>((resolve) =>
|
||||
page.on("requestfailed", (request) => {
|
||||
console.log("request failed");
|
||||
resolve(request.url());
|
||||
})
|
||||
);
|
||||
|
||||
expect(failedRequestUrl).toBe("http://wronglocalhost:3000/embed/embed.js");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user