Test/Embed/Reschedule (#6056)

* Add reschedule embed test

* Add comments
This commit is contained in:
Hariom Balhara 2022-12-19 00:54:44 +05:30 committed by GitHub
parent 7fad76ae23
commit e57f734e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 68 deletions

View File

@ -18,7 +18,7 @@ yarn dev
## Running Tests
Ensure that the main App is running on port 3000 (e.g. yarn dx) already and then run the following command:
Ensure that the main App is running on port 3000 (e.g. yarn dx) already. Also ensure dev server for embed-core is running and then run the following command:
Start the server on 3100 port
```bash
@ -31,6 +31,8 @@ And from another terminal you can run the following command to execute tests:
yarn embed-tests-quick
```
Note: `getEmbedIframe` and `addEmbedListeners` work as a team but they only support opening up embed in a fresh load. Opening an embed closing it and then opening another embed isn't supported yet.
## Shipping to Production
```bash

View File

@ -97,7 +97,10 @@
<button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Light Theme]</button>
<button data-cal-namespace="popupTeamLinkDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Dark Theme]</button>
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">See Team Links [Auto Theme]</button>
<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/qm3kwt3aTnVD7vmP9tiT2f">Reschedule Event[Auto Theme]</button>
<script>
let popupRescheduleId = new URL(document.URL).searchParams.get("popupRescheduleId") || "qm3kwt3aTnVD7vmP9tiT2f"
document.write(`<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/${popupRescheduleId}">Reschedule Event[Auto Theme]</button>`)
</script>
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">Book Paid Event [Auto Theme]</button>
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]</button>
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Auto Theme]</button>

View File

@ -137,9 +137,9 @@ expect.extend({
};
}
}
let iframeReadyCheckInterval;
const iframeReadyEventDetail = await new Promise(async (resolve) => {
setInterval(async () => {
iframeReadyCheckInterval = setInterval(async () => {
const iframeReadyEventDetail = await getActionFiredDetails({
calNamespace,
actionType: "linkReady",
@ -150,6 +150,8 @@ expect.extend({
}, 500);
});
clearInterval(iframeReadyCheckInterval);
//At this point we know that window.initialBodyVisibility would be set as DOM would already have been ready(because linkReady event can only fire after that)
const {
visibility: visibilityBefore,

View File

@ -1,6 +1,6 @@
import { test as base, Page } from "@playwright/test";
interface Fixtures {
export interface Fixtures {
addEmbedListeners: (calNamespace: string) => Promise<void>;
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>;
}

View File

@ -3,8 +3,10 @@ import { Page, Frame, test, expect } from "@playwright/test";
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: {
@ -33,31 +35,41 @@ export const getBooking = async (bookingId: string) => {
export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname: string }) => {
// We can't seem to access page.frame till contentWindow is available. So wait for that.
await page.evaluate(() => {
const iframeReady = await page.evaluate(() => {
return new Promise((resolve) => {
const iframe = document.querySelector(".cal-embed") as HTMLIFrameElement;
if (!iframe) {
resolve(false);
return;
}
const interval = setInterval(() => {
const iframe = document.querySelector(".cal-embed") as HTMLIFrameElement | null;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (iframe.contentWindow && window.iframeReady) {
if (iframe && iframe.contentWindow && window.iframeReady) {
clearInterval(interval);
resolve(true);
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
console.log("Iframe Status:", !!iframe, !!iframe?.contentWindow, window.iframeReady);
}
}, 10);
}, 500);
// A hard timeout if iframe isn't ready in that time. Avoids infinite wait
setTimeout(() => {
clearInterval(interval);
resolve(false);
}, 5000);
});
});
const embedIframe = page.frame("cal-embed");
if (!embedIframe) {
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")!;
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;
};
@ -91,7 +103,7 @@ export async function bookFirstEvent(username: string, frame: Frame, page: Page)
// 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");
const eventSlug = new URL(frame.url()).pathname;
await selectFirstAvailableTimeSlotNextMonth(frame, page);
await frame.waitForNavigation({
url(url) {
@ -104,10 +116,36 @@ export async function bookFirstEvent(username: string, frame: Frame, page: Page)
await frame.fill('[name="email"]', "embed-user@example.com");
await frame.press('[name="email"]', "Enter");
const response = await page.waitForResponse("**/api/book/event");
const responseObj = await response.json();
const bookingId = responseObj.uid;
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 bookingId;
//NOTE: frame.click('body') won't work here. Because the way it works, it clicks on the center of the body tag which is an element inside the popup view and that won't close the popup
await frame.evaluate(() => {
// Closes popup - if it is a popup. If not a popup, it will just do nothing
document.body.click();
});
return booking;
}
export async function rescheduleEvent(username, frame, page) {
await selectFirstAvailableTimeSlotNextMonth(frame, page);
await frame.waitForNavigation({
url(url) {
return url.pathname.includes(`/${username}/book`);
},
});
// --- 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;
}

View File

@ -1,66 +1,131 @@
import { expect } from "@playwright/test";
import { expect, Page } from "@playwright/test";
import { test } from "../fixtures/fixtures";
import { todo, getEmbedIframe, bookFirstEvent, getBooking, deleteAllBookingsByEmail } from "../lib/testUtils";
import { Fixtures, test } from "../fixtures/fixtures";
import {
todo,
getEmbedIframe,
bookFirstEvent,
getBooking,
deleteAllBookingsByEmail,
rescheduleEvent,
} from "../lib/testUtils";
test("should open embed iframe on click - Configured with light theme", async ({
page,
async function bookFirstFreeUserEventThroughEmbed({
addEmbedListeners,
page,
getActionFiredDetails,
}) => {
await deleteAllBookingsByEmail("embed-user@example.com");
const calNamespace = "prerendertestLightTheme";
}: {
addEmbedListeners: Fixtures["addEmbedListeners"];
page: Page;
getActionFiredDetails: Fixtures["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);
await page.goto("/?only=prerender-test");
let embedIframe = await getEmbedIframe({ page, pathname: "/free" });
expect(embedIframe).toBeFalsy();
// Goto / again so that initScript attached using addEmbedListeners can work now.
await page.goto("/");
await page.click('[data-cal-link="free?light&popup"]');
await embedButtonLocator.click();
embedIframe = await getEmbedIframe({ page, pathname: "/free" });
const embedIframe = await getEmbedIframe({ page, pathname: "/free" });
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
pathname: "/free",
});
expect(await page.screenshot()).toMatchSnapshot("event-types-list.png");
if (!embedIframe) {
throw new Error("Embed iframe not found");
}
const bookingId = await bookFirstEvent("free", embedIframe, page);
const booking = await getBooking(bookingId);
const booking = await bookFirstEvent("free", embedIframe, page);
return booking;
}
expect(booking.attendees.length).toBe(1);
await deleteAllBookingsByEmail("embed-user@example.com");
});
todo("Floating Button Test with Dark Theme");
todo("Floating Button Test with Light Theme");
todo("Add snapshot test for embed iframe");
test("should open Routing Forms embed on click", async ({
page,
addEmbedListeners,
getActionFiredDetails,
}) => {
await deleteAllBookingsByEmail("embed-user@example.com");
const calNamespace = "routingFormAuto";
await addEmbedListeners(calNamespace);
await page.goto("/?only=prerender-test");
let embedIframe = await getEmbedIframe({ 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({ 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",
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,
addEmbedListeners,
getActionFiredDetails,
}) => {
await deleteAllBookingsByEmail("embed-user@example.com");
const calNamespace = "prerendertestLightTheme";
await addEmbedListeners(calNamespace);
await page.goto("/?only=prerender-test");
let embedIframe = await getEmbedIframe({ page, pathname: "/free" });
expect(embedIframe).toBeFalsy();
await page.click('[data-cal-link="free?light&popup"]');
embedIframe = await getEmbedIframe({ page, pathname: "/free" });
await expect(embedIframe).toBeEmbedCalLink(calNamespace, 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, 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 embedIframe = await getEmbedIframe({ page, pathname: booking.eventSlug });
if (!embedIframe) {
throw new Error("Embed iframe not found");
}
await rescheduleEvent("free", embedIframe, page);
});
});
todo("Floating Button Test with Dark Theme");
todo("Floating Button Test with Light Theme");
todo("Add snapshot test for embed iframe");
test("should open Routing Forms embed on click", async ({
page,
addEmbedListeners,
getActionFiredDetails,
}) => {
await deleteAllBookingsByEmail("embed-user@example.com");
const calNamespace = "routingFormAuto";
await addEmbedListeners(calNamespace);
await page.goto("/?only=prerender-test");
let embedIframe = await getEmbedIframe({ 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({ 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();
});
await expect(embedIframe.locator("text=Seeded Form - Pro")).toBeVisible();
});