Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: flaky e2e patterns #16696

Merged
merged 18 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/web/components/booking/CancelBooking.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";

import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
Expand All @@ -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;
Expand Down Expand Up @@ -38,7 +39,7 @@ type Props = {
export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const refreshData = useRefreshData();
const { booking, allRemainingBookings, seatReferenceUid, bookingCancelledEventProps, currentUserEmail } =
props;
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -120,7 +121,7 @@ export default function CancelBooking(props: Props) {
...bookingCancelledEventProps,
booking: bookingWithCancellationReason,
});
router.refresh();
refreshData();
} else {
setLoading(false);
setError(
Expand Down
12 changes: 12 additions & 0 deletions apps/web/lib/hooks/useAsPath.ts
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 13 additions & 0 deletions apps/web/lib/hooks/useRefreshData.ts
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 3 additions & 3 deletions apps/web/playwright/availability.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/playwright/booking-limits.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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();

Expand Down
3 changes: 2 additions & 1 deletion apps/web/playwright/booking-pages.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
bookFirstEvent,
bookOptinEvent,
bookTimeSlot,
confirmReschedule,
selectFirstAvailableTimeSlotNextMonth,
testEmail,
testName,
Expand Down Expand Up @@ -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");
});
Expand Down
12 changes: 7 additions & 5 deletions apps/web/playwright/booking-seats.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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\/.*/);
Expand Down
10 changes: 4 additions & 6 deletions apps/web/playwright/change-password.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from "@playwright/test";

import { test } from "./lib/fixtures";
import { submitAndWaitForResponse } from "./lib/testUtils";

test.afterEach(({ users }) => users.deleteAll());

Expand All @@ -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(),
});
Comment on lines +22 to +24
Copy link
Contributor

@anikdhabal anikdhabal Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfect to not depending on toast messages🙏

});
});
76 changes: 31 additions & 45 deletions apps/web/playwright/change-username.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,53 @@ 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" });

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)",
},
];
Comment on lines +18 to +27
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged tests into parametized one since it was the exact same code with only username differences


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();

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("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,
},
});
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]");

expect(newUpdatedUser.username).toBe("demousernamex");
});
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]"),
});

test("User can change username to include periods(or dots)", async ({ page, users, prisma }) => {
const user = await users.create();
const newUpdatedUser = await prisma.user.findUniqueOrThrow({
where: {
id: user.id,
},
});

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(newUpdatedUser.username).toBe(item.username);
});

expect(updatedUser.username).toBe("demo.username");
});

test("User can update to PREMIUM username", async ({ page, users }, testInfo) => {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/playwright/dynamic-booking-pages.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MembershipRole } from "@calcom/prisma/client";
import { test } from "./lib/fixtures";
import {
bookTimeSlot,
confirmReschedule,
doOnOrgDomain,
selectFirstAvailableTimeSlotNextMonth,
selectSecondAvailableTimeSlotNextMonth,
Expand Down Expand Up @@ -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");
});
Expand Down
17 changes: 10 additions & 7 deletions apps/web/playwright/event-types.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
gotoFirstEventType,
saveEventType,
selectFirstAvailableTimeSlotNextMonth,
submitAndWaitForResponse,
} from "./lib/testUtils";

test.describe.configure({ mode: "parallel" });
Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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");

Expand Down Expand Up @@ -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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already handled on saveEventType

await gotoBookingPage(page);
await selectFirstAvailableTimeSlotNextMonth(page);

Expand All @@ -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);

Expand Down Expand Up @@ -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");

Expand Down
8 changes: 5 additions & 3 deletions apps/web/playwright/fixtures/bookings.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -14,7 +14,7 @@ type BookingFixture = ReturnType<typeof createBookingFixture>;
const dayjs = (...args: Parameters<typeof _dayjs>) => _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 (
Expand All @@ -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));
Comment on lines +43 to +45
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes rare collision case

const booking = await prisma.booking.create({
data: {
Expand Down
8 changes: 4 additions & 4 deletions apps/web/playwright/fixtures/regularBookings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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");
Expand Down
Loading
Loading