311 lines
12 KiB
TypeScript
311 lines
12 KiB
TypeScript
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
|
|
);
|
|
}
|