From f52497f181c9cbe1630acf043353310024525a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Fri, 20 Sep 2024 10:17:21 -0700 Subject: [PATCH] fix: flaky e2e patterns (#16696) Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> --- apps/web/components/booking/CancelBooking.tsx | 7 +- apps/web/lib/hooks/useAsPath.ts | 12 ++ apps/web/lib/hooks/useRefreshData.ts | 13 ++ apps/web/playwright/availability.e2e.ts | 6 +- apps/web/playwright/booking-limits.e2e.ts | 4 +- apps/web/playwright/booking-pages.e2e.ts | 3 +- apps/web/playwright/booking-seats.e2e.ts | 12 +- apps/web/playwright/change-password.e2e.ts | 10 +- apps/web/playwright/change-username.e2e.ts | 76 +++++------- .../playwright/dynamic-booking-pages.e2e.ts | 3 +- apps/web/playwright/event-types.e2e.ts | 17 +-- apps/web/playwright/fixtures/bookings.ts | 8 +- .../playwright/fixtures/regularBookings.ts | 8 +- apps/web/playwright/fixtures/users.ts | 5 +- apps/web/playwright/hash-my-url.e2e.ts | 13 +- .../web/playwright/integrations-stripe.e2e.ts | 4 +- apps/web/playwright/integrations.e2e.ts | 7 +- apps/web/playwright/lib/fixtures.ts | 4 +- apps/web/playwright/lib/testUtils.ts | 22 +++- .../manage-booking-questions.e2e.ts | 14 ++- .../playwright/organization/booking.e2e.ts | 17 ++- .../playwright/organization/lib/inviteUser.ts | 12 +- .../organization-invitation.e2e.ts | 1 - apps/web/playwright/profile.e2e.ts | 12 +- apps/web/playwright/reschedule.e2e.ts | 113 +++++++++--------- .../playwright/team/team-invitation.e2e.ts | 3 +- apps/web/playwright/teams.e2e.ts | 3 +- apps/web/playwright/webhook.e2e.ts | 40 +++++-- .../embed-core/playwright/lib/testUtils.ts | 6 +- 29 files changed, 262 insertions(+), 193 deletions(-) create mode 100644 apps/web/lib/hooks/useAsPath.ts create mode 100644 apps/web/lib/hooks/useRefreshData.ts diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index e9399d5b24..81d883b50a 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -1,4 +1,3 @@ -import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; import { sdkActionManager } from "@calcom/embed-core/embed-iframe"; @@ -7,6 +6,8 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco import type { RecurringEvent } from "@calcom/types/Calendar"; import { Button, Icon, TextArea } from "@calcom/ui"; +import { useRefreshData } from "@lib/hooks/useRefreshData"; + type Props = { booking: { title?: string; @@ -38,7 +39,7 @@ type Props = { export default function CancelBooking(props: Props) { const [cancellationReason, setCancellationReason] = useState(""); const { t } = useLocale(); - const router = useRouter(); + const refreshData = useRefreshData(); const { booking, allRemainingBookings, seatReferenceUid, bookingCancelledEventProps, currentUserEmail } = props; const [loading, setLoading] = useState(false); @@ -120,7 +121,7 @@ export default function CancelBooking(props: Props) { ...bookingCancelledEventProps, booking: bookingWithCancellationReason, }); - router.refresh(); + refreshData(); } else { setLoading(false); setError( diff --git a/apps/web/lib/hooks/useAsPath.ts b/apps/web/lib/hooks/useAsPath.ts new file mode 100644 index 0000000000..bf4777de20 --- /dev/null +++ b/apps/web/lib/hooks/useAsPath.ts @@ -0,0 +1,12 @@ +import { usePathname, useSearchParams } from "next/navigation"; +import { useMemo } from "react"; + +export function useAsPath() { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const asPath = useMemo( + () => `${pathname}${searchParams ? `?${searchParams.toString()}` : ""}`, + [pathname, searchParams] + ); + return asPath; +} diff --git a/apps/web/lib/hooks/useRefreshData.ts b/apps/web/lib/hooks/useRefreshData.ts new file mode 100644 index 0000000000..0040b5dd7f --- /dev/null +++ b/apps/web/lib/hooks/useRefreshData.ts @@ -0,0 +1,13 @@ +import { useRouter } from "next/navigation"; + +import { useAsPath } from "./useAsPath"; + +/** @see https://www.joshwcomeau.com/nextjs/refreshing-server-side-props/ */ +export function useRefreshData() { + const router = useRouter(); + const asPath = useAsPath(); + const refreshData = () => { + router.replace(asPath); + }; + return refreshData; +} diff --git a/apps/web/playwright/availability.e2e.ts b/apps/web/playwright/availability.e2e.ts index 1d2b504e93..961426022b 100644 --- a/apps/web/playwright/availability.e2e.ts +++ b/apps/web/playwright/availability.e2e.ts @@ -33,9 +33,9 @@ test.describe("Availablity", () => { await page.getByTestId("add-override-submit-btn").click(); await page.getByTestId("dialog-rejection").click(); await expect(page.locator('[data-testid="date-overrides-list"] > li')).toHaveCount(1); - await page.locator('[form="availability-form"][type="submit"]').click(); - const response = await page.waitForResponse("**/api/trpc/availability/schedule.update?batch=1"); - const json = await response.json(); + await submitAndWaitForResponse(page, "/api/trpc/availability/schedule.update?batch=1", { + action: () => page.locator('[form="availability-form"][type="submit"]').click(), + }); const nextMonth = dayjs().add(1, "month").startOf("month"); const troubleshooterURL = `/availability/troubleshoot?date=${nextMonth.format("YYYY-MM-DD")}`; await page.goto(troubleshooterURL); diff --git a/apps/web/playwright/booking-limits.e2e.ts b/apps/web/playwright/booking-limits.e2e.ts index 272a257c60..7da50a00fc 100644 --- a/apps/web/playwright/booking-limits.e2e.ts +++ b/apps/web/playwright/booking-limits.e2e.ts @@ -13,7 +13,7 @@ import { entries } from "@calcom/prisma/zod-utils"; import type { IntervalLimit } from "@calcom/types/Calendar"; import { test } from "./lib/fixtures"; -import { bookTimeSlot, createUserWithLimits } from "./lib/testUtils"; +import { bookTimeSlot, confirmReschedule, createUserWithLimits } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); test.afterEach(async ({ users }) => { @@ -163,7 +163,7 @@ test.describe("Booking limits", () => { await expect(page.locator('[name="name"]')).toBeDisabled(); await expect(page.locator('[name="email"]')).toBeDisabled(); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index d883f87327..416930746b 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -12,6 +12,7 @@ import { bookFirstEvent, bookOptinEvent, bookTimeSlot, + confirmReschedule, selectFirstAvailableTimeSlotNextMonth, testEmail, testName, @@ -128,7 +129,7 @@ testBothFutureAndLegacyRoutes.describe("pro user", () => { }); await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await page.waitForURL((url) => { return url.pathname.startsWith("/booking"); }); diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts index f07474af3b..cb0505a1b3 100644 --- a/apps/web/playwright/booking-seats.e2e.ts +++ b/apps/web/playwright/booking-seats.e2e.ts @@ -7,9 +7,11 @@ import { BookingStatus } from "@calcom/prisma/enums"; import { test } from "./lib/fixtures"; import { + confirmReschedule, createNewSeatedEventType, - selectFirstAvailableTimeSlotNextMonth, createUserWithSeatedEventAndAttendees, + selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); @@ -160,9 +162,9 @@ test.describe("Reschedule for booking with seats", () => { `/booking/${references[0].referenceUid}?cancel=true&seatReferenceUid=${references[0].referenceUid}` ); - await page.locator('[data-testid="confirm_cancel"]').click(); - - await page.waitForResponse((res) => res.url().includes("api/cancel") && res.status() === 200); + await submitAndWaitForResponse(page, "/api/cancel", { + action: () => page.locator('[data-testid="confirm_cancel"]').click(), + }); const oldBooking = await prisma.booking.findFirst({ where: { uid: booking.uid }, @@ -396,7 +398,7 @@ test.describe("Reschedule for booking with seats", () => { await expect(reasonElement).toBeVisible(); // expect to be redirected to reschedule page - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); // should wait for URL but that path starts with booking/ await page.waitForURL(/\/booking\/.*/); diff --git a/apps/web/playwright/change-password.e2e.ts b/apps/web/playwright/change-password.e2e.ts index bfb3ad75f9..b37d900eff 100644 --- a/apps/web/playwright/change-password.e2e.ts +++ b/apps/web/playwright/change-password.e2e.ts @@ -1,6 +1,7 @@ import { expect } from "@playwright/test"; import { test } from "./lib/fixtures"; +import { submitAndWaitForResponse } from "./lib/testUtils"; test.afterEach(({ users }) => users.deleteAll()); @@ -18,11 +19,8 @@ test.describe("Change Password Test", () => { const $newPasswordField = page.locator('[name="newPassword"]'); $newPasswordField.fill(`${pro.username}Aa1111`); - - await page.locator("text=Update").click(); - - const toast = await page.waitForSelector('[data-testid="toast-success"]'); - - expect(toast).toBeTruthy(); + await submitAndWaitForResponse(page, "/api/trpc/auth/changePassword?batch=1", { + action: () => page.locator("text=Update").click(), + }); }); }); diff --git a/apps/web/playwright/change-username.e2e.ts b/apps/web/playwright/change-username.e2e.ts index 6c15fb42dd..658ab33b51 100644 --- a/apps/web/playwright/change-username.e2e.ts +++ b/apps/web/playwright/change-username.e2e.ts @@ -7,7 +7,7 @@ import { MembershipRole } from "@calcom/prisma/enums"; import { moveUserToOrg } from "@lib/orgMigration"; import { test } from "./lib/fixtures"; -import { IS_STRIPE_ENABLED } from "./lib/testUtils"; +import { IS_STRIPE_ENABLED, submitAndWaitForResponse } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); @@ -15,59 +15,45 @@ const IS_SELF_HOSTED = !( new URL(WEBAPP_URL).hostname.endsWith(".cal.dev") || !!new URL(WEBAPP_URL).hostname.endsWith(".cal.com") ); +const TESTING_USERNAMES = [ + { + username: "demousernamex", + description: "", + }, + { + username: "demo.username", + description: " to include periods(or dots)", + }, +]; + test.describe("Change username on settings", () => { test.afterEach(async ({ users }) => { await users.deleteAll(); }); - test("User can change username", async ({ page, users, prisma }) => { - const user = await users.create(); + TESTING_USERNAMES.forEach((item) => { + test(`User can change username${item.description}`, async ({ page, users, prisma }) => { + const user = await users.create(); + await user.apiLogin(); + // Try to go homepage + await page.goto("/settings/my-account/profile"); + // Change username from normal to normal + const usernameInput = page.locator("[data-testid=username-input]"); - await user.apiLogin(); - // Try to go homepage - await page.goto("/settings/my-account/profile"); - // Change username from normal to normal - const usernameInput = page.locator("[data-testid=username-input]"); + await usernameInput.fill(item.username); + await page.click("[data-testid=update-username-btn]"); + await submitAndWaitForResponse(page, "/api/trpc/viewer/updateProfile?batch=1", { + action: () => page.click("[data-testid=save-username]"), + }); - await usernameInput.fill("demousernamex"); - await page.click("[data-testid=update-username-btn]"); - await Promise.all([ - page.click("[data-testid=save-username]"), - page.getByTestId("toast-success").waitFor(), - ]); + const newUpdatedUser = await prisma.user.findUniqueOrThrow({ + where: { + id: user.id, + }, + }); - const newUpdatedUser = await prisma.user.findUniqueOrThrow({ - where: { - id: user.id, - }, + expect(newUpdatedUser.username).toBe(item.username); }); - - expect(newUpdatedUser.username).toBe("demousernamex"); - }); - - test("User can change username to include periods(or dots)", async ({ page, users, prisma }) => { - const user = await users.create(); - - await user.apiLogin(); - // Try to go homepage - await page.goto("/settings/my-account/profile"); - // Change username from normal to normal - const usernameInput = page.locator("[data-testid=username-input]"); - // User can change username to include dots(or periods) - await usernameInput.fill("demo.username"); - await page.click("[data-testid=update-username-btn]"); - await Promise.all([ - page.click("[data-testid=save-username]"), - page.getByTestId("toast-success").waitFor(), - ]); - - const updatedUser = await prisma.user.findUniqueOrThrow({ - where: { - id: user.id, - }, - }); - - expect(updatedUser.username).toBe("demo.username"); }); test("User can update to PREMIUM username", async ({ page, users }, testInfo) => { diff --git a/apps/web/playwright/dynamic-booking-pages.e2e.ts b/apps/web/playwright/dynamic-booking-pages.e2e.ts index bc0f9d8d39..bb8fce8e78 100644 --- a/apps/web/playwright/dynamic-booking-pages.e2e.ts +++ b/apps/web/playwright/dynamic-booking-pages.e2e.ts @@ -5,6 +5,7 @@ import { MembershipRole } from "@calcom/prisma/client"; import { test } from "./lib/fixtures"; import { bookTimeSlot, + confirmReschedule, doOnOrgDomain, selectFirstAvailableTimeSlotNextMonth, selectSecondAvailableTimeSlotNextMonth, @@ -42,7 +43,7 @@ test("dynamic booking", async ({ page, users }) => { await selectSecondAvailableTimeSlotNextMonth(page); // No need to fill fields since they should be already filled - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await page.waitForURL((url) => { return url.pathname.startsWith("/booking"); }); diff --git a/apps/web/playwright/event-types.e2e.ts b/apps/web/playwright/event-types.e2e.ts index 1fe8d5592b..4b20305c5c 100644 --- a/apps/web/playwright/event-types.e2e.ts +++ b/apps/web/playwright/event-types.e2e.ts @@ -13,6 +13,7 @@ import { gotoFirstEventType, saveEventType, selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); @@ -130,9 +131,9 @@ testBothFutureAndLegacyRoutes.describe("Event Types tests", () => { await page.waitForURL((url) => { return !!url.pathname.match(/\/event-types\/.+/); }); - await page.locator("[data-testid=update-eventtype]").click(); - const toast = await page.waitForSelector('[data-testid="toast-success"]'); - expect(toast).toBeTruthy(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); }); test("can add multiple organizer address", async ({ page }) => { @@ -153,7 +154,9 @@ testBothFutureAndLegacyRoutes.describe("Event Types tests", () => { await page.locator("[data-testid=add-location]").click(); await fillLocation(page, locationData[2], 2); - await page.locator("[data-testid=update-eventtype]").click(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); await page.goto("/event-types"); @@ -224,7 +227,6 @@ testBothFutureAndLegacyRoutes.describe("Event Types tests", () => { await page.locator(`text="Cal Video (Global)"`).click(); await saveEventType(page); - await page.getByTestId("toast-success").waitFor(); await gotoBookingPage(page); await selectFirstAvailableTimeSlotNextMonth(page); @@ -246,7 +248,6 @@ testBothFutureAndLegacyRoutes.describe("Event Types tests", () => { await page.locator(`input[name="${locationInputName}"]`).fill(testUrl); await saveEventType(page); - await page.getByTestId("toast-success").waitFor(); await gotoBookingPage(page); await selectFirstAvailableTimeSlotNextMonth(page); @@ -298,7 +299,9 @@ testBothFutureAndLegacyRoutes.describe("Event Types tests", () => { const locationAddress = "New Delhi"; await fillLocation(page, locationAddress, 0, false); - await page.locator("[data-testid=update-eventtype]").click(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); await page.goto("/event-types"); diff --git a/apps/web/playwright/fixtures/bookings.ts b/apps/web/playwright/fixtures/bookings.ts index ad2e6f072c..d7b1a7f862 100644 --- a/apps/web/playwright/fixtures/bookings.ts +++ b/apps/web/playwright/fixtures/bookings.ts @@ -1,4 +1,4 @@ -import type { Page } from "@playwright/test"; +import type { Page, WorkerInfo } from "@playwright/test"; import type { Booking, Prisma } from "@prisma/client"; import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; @@ -14,7 +14,7 @@ type BookingFixture = ReturnType; const dayjs = (...args: Parameters) => _dayjs(...args).tz("Europe/London"); // creates a user fixture instance and stores the collection -export const createBookingsFixture = (page: Page) => { +export const createBookingsFixture = (page: Page, workerInfo: WorkerInfo) => { const store = { bookings: [], page } as { bookings: BookingFixture[]; page: typeof page }; return { create: async ( @@ -40,7 +40,9 @@ export const createBookingsFixture = (page: Page) => { endDateParam?: Date ) => { const startDate = startDateParam || dayjs().add(1, "day").toDate(); - const seed = `${username}:${dayjs(startDate).utc().format()}:${new Date().getTime()}`; + const seed = `${username}:${dayjs(startDate).utc().format()}:${new Date().getTime()}:${ + workerInfo.workerIndex + }`; const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL)); const booking = await prisma.booking.create({ data: { diff --git a/apps/web/playwright/fixtures/regularBookings.ts b/apps/web/playwright/fixtures/regularBookings.ts index a941cd14d3..61ea24c4f1 100644 --- a/apps/web/playwright/fixtures/regularBookings.ts +++ b/apps/web/playwright/fixtures/regularBookings.ts @@ -2,7 +2,7 @@ import { expect, type Page } from "@playwright/test"; import type { MembershipRole } from "@calcom/prisma/enums"; -import { localize } from "../lib/testUtils"; +import { localize, submitAndWaitForResponse } from "../lib/testUtils"; import type { createUsersFixture } from "./users"; export const scheduleSuccessfullyText = "This meeting is scheduled"; @@ -40,9 +40,9 @@ export function createBookingPageFixture(page: Page) { await page.goto("/event-types"); }, updateEventType: async () => { - await page.getByTestId("update-eventtype").click(); - const toast = await page.waitForSelector('[data-testid="toast-success"]'); - expect(toast).toBeTruthy(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); }, previewEventType: async () => { const eventtypePromise = page.waitForEvent("popup"); diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts index 5703d5b67a..16584a3a45 100644 --- a/apps/web/playwright/fixtures/users.ts +++ b/apps/web/playwright/fixtures/users.ts @@ -1014,10 +1014,11 @@ export async function login( await emailLocator.fill(user.email ?? `${user.username}@example.com`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await passwordLocator.fill(user.password ?? user.username!); - await signInLocator.click(); // waiting for specific login request to resolve - await page.waitForResponse(/\/api\/auth\/callback\/credentials/); + const responsePromise = page.waitForResponse(/\/api\/auth\/callback\/credentials/); + await signInLocator.click(); + await responsePromise; } export async function apiLogin( diff --git a/apps/web/playwright/hash-my-url.e2e.ts b/apps/web/playwright/hash-my-url.e2e.ts index f6fbb0cefe..c83effd1c0 100644 --- a/apps/web/playwright/hash-my-url.e2e.ts +++ b/apps/web/playwright/hash-my-url.e2e.ts @@ -1,7 +1,11 @@ import { expect } from "@playwright/test"; import { test } from "./lib/fixtures"; -import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils"; +import { + bookTimeSlot, + selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, +} from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); @@ -55,10 +59,9 @@ test.describe("hash my url", () => { await page.getByTestId("vertical-tab-event_setup_tab_title").click(); await page.locator("[data-testid=event-title]").first().fill("somethingrandom"); await page.locator("[data-testid=event-slug]").first().fill("somethingrandom"); - const submitPromise = page.waitForResponse("/api/trpc/eventTypes/update?batch=1"); - await page.locator("[data-testid=update-eventtype]").click(); - const response = await submitPromise; - expect(response.status()).toBe(200); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); await page.locator(".primary-navigation >> text=Advanced").click(); const $url2 = await page.locator('//*[@data-testid="generated-hash-url"]').inputValue(); expect($url2.includes("somethingrandom")).toBeTruthy(); diff --git a/apps/web/playwright/integrations-stripe.e2e.ts b/apps/web/playwright/integrations-stripe.e2e.ts index 7e6754726a..e4a51a3d25 100644 --- a/apps/web/playwright/integrations-stripe.e2e.ts +++ b/apps/web/playwright/integrations-stripe.e2e.ts @@ -7,7 +7,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { test, todo } from "./lib/fixtures"; import type { Fixtures } from "./lib/fixtures"; -import { IS_STRIPE_ENABLED, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils"; +import { confirmReschedule, IS_STRIPE_ENABLED, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); test.afterEach(({ users }) => users.deleteAll()); @@ -438,7 +438,7 @@ test.describe("Stripe integration with the new app install flow skip false", () await selectFirstAvailableTimeSlotNextMonth(page); - await page.click('[data-testid="confirm-reschedule-button"]'); + await confirmReschedule(page); await expect(page.getByText("This meeting is scheduled")).toBeVisible(); }); diff --git a/apps/web/playwright/integrations.e2e.ts b/apps/web/playwright/integrations.e2e.ts index 657f0d0aab..d6474af2a5 100644 --- a/apps/web/playwright/integrations.e2e.ts +++ b/apps/web/playwright/integrations.e2e.ts @@ -8,6 +8,7 @@ import { v4 as uuidv4 } from "uuid"; import { prisma } from "@calcom/prisma"; import { test, todo } from "./lib/fixtures"; +import { submitAndWaitForResponse } from "./lib/testUtils"; declare let global: { E2E_EMAILS?: ({ text: string } | Record)[]; @@ -130,8 +131,10 @@ async function bookEvent(page: Page, calLink: string) { // --- fill form await page.fill('[name="name"]', "Integration User"); await page.fill('[name="email"]', "integration-user@example.com"); - await page.press('[name="email"]', "Enter"); - const response = await page.waitForResponse("**/api/book/event"); + + const response = await submitAndWaitForResponse(page, "**/api/book/event", { + action: () => page.press('[name="email"]', "Enter"), + }); const responseObj = await response.json(); const bookingId = responseObj.uid; await page.waitForSelector("[data-testid=success-page]"); diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts index 7ed8bdc8bc..5ff170062a 100644 --- a/apps/web/playwright/lib/fixtures.ts +++ b/apps/web/playwright/lib/fixtures.ts @@ -68,8 +68,8 @@ export const test = base.extend({ const usersFixture = createUsersFixture(page, emails, workerInfo); await use(usersFixture); }, - bookings: async ({ page }, use) => { - const bookingsFixture = createBookingsFixture(page); + bookings: async ({ page }, use, workerInfo) => { + const bookingsFixture = createBookingsFixture(page, workerInfo); await use(bookingsFixture); }, payments: async ({ page }, use) => { diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index dfbd98712f..a5311bf555 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -218,9 +218,9 @@ export async function installAppleCalendar(page: Page) { } export async function getInviteLink(page: Page) { - const response = await page.waitForResponse("/api/trpc/teams/createInvite?batch=1"); - expect(response.status()).toBe(200); - const json = await response.json(); + const json = await submitAndWaitForResponse(page, "/api/trpc/teams/createInvite?batch=1", { + action: () => page.locator(`[data-testid="copy-invite-link-button"]`).click(), + }); return json[0].result.data.json.inviteLink as string; } @@ -428,10 +428,15 @@ export async function gotoBookingPage(page: Page) { } export async function saveEventType(page: Page) { - await page.locator("[data-testid=update-eventtype]").click(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); } -/** Fastest way so far to test for saving changes and form submissions */ +/** + * Fastest way so far to test for saving changes and form submissions + * @see https://playwright.dev/docs/api/class-page#page-wait-for-response + */ export async function submitAndWaitForResponse( page: Page, url: string, @@ -441,4 +446,11 @@ export async function submitAndWaitForResponse( await action(); const response = await submitPromise; expect(response.status()).toBe(expectedStatusCode); + return response.json(); +} + +export async function confirmReschedule(page: Page, url = "/api/book/event") { + await submitAndWaitForResponse(page, url, { + action: () => page.locator('[data-testid="confirm-reschedule-button"]').click(), + }); } diff --git a/apps/web/playwright/manage-booking-questions.e2e.ts b/apps/web/playwright/manage-booking-questions.e2e.ts index de9e347a6d..e9cbc076b8 100644 --- a/apps/web/playwright/manage-booking-questions.e2e.ts +++ b/apps/web/playwright/manage-booking-questions.e2e.ts @@ -10,8 +10,12 @@ import { WebhookTriggerEvents } from "@calcom/prisma/enums"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { test } from "./lib/fixtures"; -import { createNewEventType } from "./lib/testUtils"; -import { createHttpServer, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils"; +import { + createHttpServer, + createNewEventType, + selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, +} from "./lib/testUtils"; function getLabelLocator(field: Locator) { // There are 2 labels right now. Will be one in future. The second one is hidden @@ -655,7 +659,7 @@ async function rescheduleFromTheLinkOnPage({ page }: { page: Page }) { await page.locator('[data-testid="reschedule-link"]').click(); await page.waitForLoadState(); await selectFirstAvailableTimeSlotNextMonth(page); - await page.click('[data-testid="confirm-reschedule-button"]'); + await page.locator('[data-testid="confirm-reschedule-button"]').click(); } async function openBookingFormInPreviewTab(context: PlaywrightTestArgs["context"], page: Page) { @@ -668,7 +672,9 @@ async function openBookingFormInPreviewTab(context: PlaywrightTestArgs["context" } async function saveEventType(page: Page) { - await page.locator("[data-testid=update-eventtype]").click(); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); } async function addWebhook( diff --git a/apps/web/playwright/organization/booking.e2e.ts b/apps/web/playwright/organization/booking.e2e.ts index a4dca7a6f9..5fb2a9c45b 100644 --- a/apps/web/playwright/organization/booking.e2e.ts +++ b/apps/web/playwright/organization/booking.e2e.ts @@ -12,6 +12,7 @@ import { bookTimeSlot, doOnOrgDomain, selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, testName, } from "../lib/testUtils"; import { expectExistingUserToBeInvitedToOrganization } from "../team/expects"; @@ -660,11 +661,9 @@ const markPhoneNumberAsRequiredAndEmailAsOptional = async (page: Page, eventId: const emailRequiredFiled = await page.locator('[data-testid="field-required"]'); await emailRequiredFiled.locator("> :nth-child(2)").click(); await page.getByTestId("field-add-save").click(); - - const submitPromise = page.waitForResponse("/api/trpc/eventTypes/update?batch=1"); - await page.locator("[data-testid=update-eventtype]").click(); - const response = await submitPromise; - expect(response.status()).toBe(200); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); }; const markPhoneNumberAsRequiredField = async (page: Page, eventId: number) => { @@ -675,9 +674,7 @@ const markPhoneNumberAsRequiredField = async (page: Page, eventId: number) => { const phoneRequiredFiled = await page.locator('[data-testid="field-required"]'); await phoneRequiredFiled.locator("> :nth-child(1)").click(); await page.getByTestId("field-add-save").click(); - - const submitPromise = page.waitForResponse("/api/trpc/eventTypes/update?batch=1"); - await page.locator("[data-testid=update-eventtype]").click(); - const response = await submitPromise; - expect(response.status()).toBe(200); + await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", { + action: () => page.locator("[data-testid=update-eventtype]").click(), + }); }; diff --git a/apps/web/playwright/organization/lib/inviteUser.ts b/apps/web/playwright/organization/lib/inviteUser.ts index b1776ae69e..f5f38108ff 100644 --- a/apps/web/playwright/organization/lib/inviteUser.ts +++ b/apps/web/playwright/organization/lib/inviteUser.ts @@ -1,6 +1,8 @@ import type { Page } from "@playwright/test"; import type { createUsersFixture } from "playwright/fixtures/users"; +import { submitAndWaitForResponse } from "../../lib/testUtils"; + export const inviteUserToOrganization = async ({ page, organizationId, @@ -35,19 +37,21 @@ export const inviteExistingUserToOrganization = async ({ usersFixture: ReturnType; }) => { await page.goto("/settings/organizations/members"); - await inviteAnEmail(page, user.email); - await page.waitForSelector('[data-testid="toast-success"]'); return { invitedUserEmail: user.email }; }; export async function acceptTeamOrOrgInvite(page: Page) { await page.goto("/settings/teams"); - await page.click('[data-testid^="accept-invitation"]'); + await submitAndWaitForResponse(page, "/api/trpc/teams/acceptOrLeave?batch=1", { + action: () => page.click('[data-testid^="accept-invitation"]'), + }); } async function inviteAnEmail(page: Page, invitedUserEmail: string) { await page.locator('button:text("Add")').click(); await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); - await page.locator('button:text("Send invite")').click(); + await submitAndWaitForResponse(page, "/api/trpc/teams/inviteMember?batch=1", { + action: () => page.locator('button:text("Send invite")').click(), + }); } diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts index 1755e2697e..82f783fa09 100644 --- a/apps/web/playwright/organization/organization-invitation.e2e.ts +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -558,7 +558,6 @@ function assertInviteLink(inviteLink: string | null | undefined): asserts invite async function copyInviteLink(page: Page) { await page.locator('button:text("Add")').click(); - await page.locator(`[data-testid="copy-invite-link-button"]`).click(); const inviteLink = await getInviteLink(page); return inviteLink; } diff --git a/apps/web/playwright/profile.e2e.ts b/apps/web/playwright/profile.e2e.ts index 5b8c8f318e..e7f88b768a 100644 --- a/apps/web/playwright/profile.e2e.ts +++ b/apps/web/playwright/profile.e2e.ts @@ -7,7 +7,7 @@ import type { PrismaClient } from "@calcom/prisma"; import type { createEmailsFixture } from "./fixtures/emails"; import { test } from "./lib/fixtures"; -import { getEmailsReceivedByUser } from "./lib/testUtils"; +import { getEmailsReceivedByUser, submitAndWaitForResponse } from "./lib/testUtils"; import { expectInvitationEmailToBeReceived } from "./team/expects"; test.describe.configure({ mode: "parallel" }); @@ -62,13 +62,9 @@ test.describe("Update Profile", () => { await page.getByTestId("password").fill(user?.username ?? "Nameless User"); - await page.getByTestId("profile-update-email-submit-button").click(); - - const toastLocator = await page.getByTestId("toast-success"); - - const textContent = await toastLocator.textContent(); - - expect(textContent).toContain(email); + await submitAndWaitForResponse(page, "/api/trpc/viewer/updateProfile?batch=1", { + action: () => page.getByTestId("profile-update-email-submit-button").click(), + }); // Instead of dealing with emails in e2e lets just get the token and navigate to it const verificationToken = await prisma.verificationToken.findFirst({ diff --git a/apps/web/playwright/reschedule.e2e.ts b/apps/web/playwright/reschedule.e2e.ts index f1b0d90769..174a3dfdc6 100644 --- a/apps/web/playwright/reschedule.e2e.ts +++ b/apps/web/playwright/reschedule.e2e.ts @@ -6,13 +6,16 @@ import { MembershipRole } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; +import { apiLogin } from "./fixtures/users"; import { test } from "./lib/fixtures"; import { - selectFirstAvailableTimeSlotNextMonth, bookTimeSlot, + confirmReschedule, doOnOrgDomain, goToUrlWithErrorHandling, IS_STRIPE_ENABLED, + selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, } from "./lib/testUtils"; test.describe.configure({ mode: "parallel" }); @@ -93,9 +96,7 @@ test.describe("Reschedule Tests", async () => { await expect(page.locator('[name="name"]')).toBeDisabled(); await expect(page.locator('[name="email"]')).toBeDisabled(); - - await page.locator('[data-testid="confirm-reschedule-button"]').click(); - + await confirmReschedule(page); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); const newBooking = await prisma.booking.findFirstOrThrow({ where: { fromReschedule: booking.uid } }); @@ -147,7 +148,7 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await page.waitForURL((url) => { return url.pathname.indexOf("/payment") > -1; @@ -177,7 +178,7 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); }); @@ -194,13 +195,13 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); - const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: booking?.uid } }); + const newBooking = await prisma.booking.findFirstOrThrow({ where: { fromReschedule: booking?.uid } }); expect(newBooking).not.toBeNull(); - expect(newBooking?.status).toBe(BookingStatus.PENDING); + expect(newBooking.status).toBe(BookingStatus.PENDING); }); test("Opt in event should be ACCEPTED when rescheduled by OWNER", async ({ page, users, bookings }) => { @@ -216,13 +217,13 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); - const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: booking?.uid } }); + const newBooking = await prisma.booking.findFirstOrThrow({ where: { fromReschedule: booking?.uid } }); expect(newBooking).not.toBeNull(); - expect(newBooking?.status).toBe(BookingStatus.ACCEPTED); + expect(newBooking.status).toBe(BookingStatus.ACCEPTED); }); test("Attendee should be able to reschedule a booking", async ({ page, users, bookings }) => { @@ -235,13 +236,13 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); - const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: booking?.uid } }); + const newBooking = await prisma.booking.findFirstOrThrow({ where: { fromReschedule: booking?.uid } }); expect(newBooking).not.toBeNull(); - expect(newBooking?.status).toBe(BookingStatus.ACCEPTED); + expect(newBooking.status).toBe(BookingStatus.ACCEPTED); }); test("Should be able to book slot that overlaps with original rescheduled booking", async ({ @@ -270,26 +271,31 @@ test.describe("Reschedule Tests", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); }); - test("Should load Valid Cal video url after rescheduling Opt in events", async ({ page, users, bookings, + browser, }) => { + // eslint-disable-next-line playwright/no-skipped-test + test.skip(!process.env.DAILY_API_KEY, "DAILY_API_KEY is needed for this test"); const user = await users.create(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const eventType = user.eventTypes.find((e) => e.slug === "opt-in")!; const confirmBooking = async (bookingId: number) => { - await user.apiLogin(); - await page.goto("/bookings/upcoming"); - const elem = await page.locator(`[data-bookingid="${bookingId}"][data-testid="confirm"]`); - await elem.click(); - await page.getByTestId("toast-success").waitFor(); - await user.logout(); + const authedContext = await browser.newContext(); + const authedPage = await authedContext.newPage(); + const userSnapshot = await user.self(); + await apiLogin({ ...userSnapshot, password: userSnapshot.username }, authedPage); + await authedPage.goto("/bookings/upcoming"); + await submitAndWaitForResponse(authedPage, "/api/trpc/bookings/confirm?batch=1", { + action: () => authedPage.locator(`[data-bookingid="${bookingId}"][data-testid="confirm"]`).click(), + }); + await authedContext.close(); }; await page.goto(`/${user.username}/${eventType.slug}`); @@ -301,38 +307,34 @@ test.describe("Reschedule Tests", async () => { const pathSegments = pageUrl.pathname.split("/"); const bookingUID = pathSegments[pathSegments.length - 1]; - const currentBooking = await prisma.booking.findFirst({ where: { uid: bookingUID } }); + const currentBooking = await prisma.booking.findFirstOrThrow({ where: { uid: bookingUID } }); expect(currentBooking).not.toBeUndefined(); + await confirmBooking(currentBooking.id); + + await page.goto(`/reschedule/${currentBooking.uid}`); + await selectFirstAvailableTimeSlotNextMonth(page); + + await confirmReschedule(page); + await expect(page).toHaveURL(/.*booking/); + + const newBooking = await prisma.booking.findFirstOrThrow({ + where: { fromReschedule: currentBooking.uid }, + }); + expect(newBooking).not.toBeUndefined(); + expect(newBooking.status).toBe(BookingStatus.PENDING); + await confirmBooking(newBooking.id); + + const booking = await prisma.booking.findFirstOrThrow({ where: { id: newBooking.id } }); + expect(booking).not.toBeUndefined(); + expect(booking.status).toBe(BookingStatus.ACCEPTED); + + const locationVideoCallUrl = bookingMetadataSchema.parse(booking.metadata || {})?.videoCallUrl; + // FIXME: This should be consistent or skip the whole test // eslint-disable-next-line playwright/no-conditional-in-test - if (currentBooking) { - await confirmBooking(currentBooking.id); - - await page.goto(`/reschedule/${currentBooking.uid}`); - await selectFirstAvailableTimeSlotNextMonth(page); - - await page.locator('[data-testid="confirm-reschedule-button"]').click(); - await expect(page).toHaveURL(/.*booking/); - - const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: currentBooking.uid } }); - expect(newBooking).not.toBeUndefined(); - expect(newBooking?.status).toBe(BookingStatus.PENDING); - // eslint-disable-next-line playwright/no-conditional-in-test - if (newBooking) { - await confirmBooking(newBooking?.id); - - const booking = await prisma.booking.findFirst({ where: { id: newBooking.id } }); - expect(booking).not.toBeUndefined(); - expect(booking?.status).toBe(BookingStatus.ACCEPTED); - const locationVideoCallUrl = bookingMetadataSchema.parse(booking?.metadata || {})?.videoCallUrl; - expect(locationVideoCallUrl).not.toBeUndefined(); - - // eslint-disable-next-line playwright/no-conditional-in-test - if (booking && locationVideoCallUrl) { - await page.goto(locationVideoCallUrl); - await expect(page.frameLocator("iFrame").locator('text="Continue"')).toBeVisible(); - } - } - } + if (!locationVideoCallUrl) return; + expect(locationVideoCallUrl).not.toBeUndefined(); + await page.goto(locationVideoCallUrl); + await expect(page.frameLocator("iFrame").locator('text="Continue"')).toBeVisible(); }); test("Should be able to a dynamic group booking", async () => { @@ -409,7 +411,10 @@ test.describe("Reschedule Tests", async () => { async function expectSuccessfulReschedule() { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator("[data-testid=confirm-reschedule-button]").click(); + const { protocol, host } = new URL(page.url()); + // Needed since we we're expecting a non-org URL, causing timeouts. + const url = getNonOrgUrlFromOrgUrl(`${protocol}//${host}/api/book/event`); + await confirmReschedule(page, url); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); } }); diff --git a/apps/web/playwright/team/team-invitation.e2e.ts b/apps/web/playwright/team/team-invitation.e2e.ts index cb05963b10..add1d02d74 100644 --- a/apps/web/playwright/team/team-invitation.e2e.ts +++ b/apps/web/playwright/team/team-invitation.e2e.ts @@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { test } from "../lib/fixtures"; -import { localize, getInviteLink } from "../lib/testUtils"; +import { getInviteLink, localize } from "../lib/testUtils"; import { expectInvitationEmailToBeReceived } from "./expects"; test.describe.configure({ mode: "parallel" }); @@ -75,7 +75,6 @@ test.describe("Team", () => { password: "P4ssw0rd!", }); await page.locator(`button:text("${t("add")}")`).click(); - await page.locator(`[data-testid="copy-invite-link-button"]`).click(); const inviteLink = await getInviteLink(page); const context = await browser.newContext(); diff --git a/apps/web/playwright/teams.e2e.ts b/apps/web/playwright/teams.e2e.ts index bb550a03c4..32ac1830c6 100644 --- a/apps/web/playwright/teams.e2e.ts +++ b/apps/web/playwright/teams.e2e.ts @@ -8,6 +8,7 @@ import { test, todo } from "./lib/fixtures"; import { testBothFutureAndLegacyRoutes } from "./lib/future-legacy-routes"; import { bookTimeSlot, + confirmReschedule, fillStripeTestCheckout, selectFirstAvailableTimeSlotNextMonth, testName, @@ -300,7 +301,7 @@ testBothFutureAndLegacyRoutes.describe("Teams - NonOrg", (routeVariant) => { const booking = await bookings.create(owner.id, owner.username, eventType.id); await page.goto(`/reschedule/${booking.uid}`); await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator("[data-testid=confirm-reschedule-button]").click(); + await confirmReschedule(page); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); }); todo("Reschedule a Round Robin EventType booking"); diff --git a/apps/web/playwright/webhook.e2e.ts b/apps/web/playwright/webhook.e2e.ts index 1e6f70b419..b3481f4b43 100644 --- a/apps/web/playwright/webhook.e2e.ts +++ b/apps/web/playwright/webhook.e2e.ts @@ -9,9 +9,11 @@ import { test } from "./lib/fixtures"; import { bookOptinEvent, bookTimeSlot, + confirmReschedule, createUserWithSeatedEventAndAttendees, gotoRoutingLink, selectFirstAvailableTimeSlotNextMonth, + submitAndWaitForResponse, } from "./lib/testUtils"; // remove dynamic properties that differs depending on where you run the tests @@ -145,8 +147,10 @@ test.describe("BOOKING_REJECTED", async () => { const webhookReceiver = await webhooks.createReceiver(); await page.goto("/bookings/unconfirmed"); await page.click('[data-testid="reject"]'); - await page.click('[data-testid="rejection-confirm"]'); - await page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm")); + + await submitAndWaitForResponse(page, "/api/trpc/bookings/confirm?batch=1", { + action: () => page.click('[data-testid="rejection-confirm"]'), + }); await webhookReceiver.waitForRequestCount(1); @@ -358,7 +362,7 @@ test.describe("BOOKING_RESCHEDULED", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page.getByTestId("success-page")).toBeVisible(); @@ -426,7 +430,7 @@ test.describe("BOOKING_RESCHEDULED", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page.getByTestId("success-page")).toBeVisible(); @@ -459,7 +463,7 @@ test.describe("BOOKING_RESCHEDULED", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await page.locator('[data-testid="confirm-reschedule-button"]').click(); + await confirmReschedule(page); await expect(page).toHaveURL(/.*booking/); @@ -577,10 +581,28 @@ test.describe("MEETING_ENDED, MEETING_STARTED", async () => { expect(newMeetingEndedTriggers.length).toBe(0); // disable webhook - const submitPromise = page.waitForResponse("/api/trpc/webhook/edit?batch=1"); - await page.getByTestId("webhook-switch").click(); - const response = await submitPromise; - expect(response.status()).toBe(200); + await submitAndWaitForResponse(page, "/api/trpc/webhook/edit?batch=1", { + action: () => page.getByTestId("webhook-switch").click(), + }); + + const scheduledTriggersAfterDisabling = await prisma.webhookScheduledTriggers.findMany({ + where: { + webhook: { + userId: user.id, + }, + }, + select: { + payload: true, + webhook: { + select: { + userId: true, + }, + }, + startAfter: true, + }, + }); + + expect(scheduledTriggersAfterDisabling.length).toBe(0); }); }); diff --git a/packages/embeds/embed-core/playwright/lib/testUtils.ts b/packages/embeds/embed-core/playwright/lib/testUtils.ts index a2230cb352..8b9a5b1df5 100644 --- a/packages/embeds/embed-core/playwright/lib/testUtils.ts +++ b/packages/embeds/embed-core/playwright/lib/testUtils.ts @@ -102,8 +102,9 @@ export async function bookFirstEvent(username: string, frame: Frame, page: Page) // --- fill form await frame.fill('[name="name"]', "Embed User"); await frame.fill('[name="email"]', "embed-user@example.com"); + const responsePromise = page.waitForResponse("**/api/book/event"); await frame.press('[name="email"]', "Enter"); - const response = await page.waitForResponse("**/api/book/event"); + const response = await responsePromise; const booking = (await response.json()) as { uid: string; eventSlug: string }; expect(response.status()).toBe(200); booking.eventSlug = eventSlug; @@ -114,8 +115,9 @@ export async function rescheduleEvent(username: string, frame: Frame, page: Page await selectFirstAvailableTimeSlotNextMonth(frame, page); // --- fill form await frame.press('[name="email"]', "Enter"); + const responsePromise = page.waitForResponse("**/api/book/event"); await frame.click("[data-testid=confirm-reschedule-button]"); - const response = await page.waitForResponse("**/api/book/event"); + const response = await responsePromise; expect(response.status()).toBe(200); const responseObj = await response.json(); const booking = responseObj.uid;