chore: enable booking redesign for e2e (#25642)

* enable bookings-v3 feature flag for e2e tests WIP

* fix e2e

* update comments
This commit is contained in:
Eunjae Lee authored and GitHub committed 2025-12-17 10:09:09 +00:00
1 parent c2d275551a
commit c361b9bbc5
3 files changed
+118 -34

No files matched your search

+12 -6
View File
@@ -555,17 +555,23 @@ test.describe("Reschedule for booking with seats", () => {
await page.waitForSelector('[data-testid="bookings"]');
await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click();
await page.waitForTimeout(2000);
const href = await page.locator('[data-testid="reschedule"]').getAttribute("href");
// Wait for the reschedule link to be visible and have an href attribute
const rescheduleLink = page.locator('[data-testid="reschedule"]');
await expect(rescheduleLink).toBeVisible();
await expect(rescheduleLink).toHaveAttribute("href", /.+/);
const href = await rescheduleLink.getAttribute("href");
const url = new URL(href!, page.url());
const seatReferenceUid = url.searchParams.get('seatReferenceUid');
if(!seatReferenceUid) {
const seatReferenceUid = url.searchParams.get("seatReferenceUid");
if (!seatReferenceUid) {
await page.reload();
await page.waitForSelector('[data-testid="bookings"]');
await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click();
await page.waitForTimeout(2000);
await expect(rescheduleLink).toBeVisible();
await expect(rescheduleLink).toHaveAttribute("href", /.+/);
}
await page.locator('[data-testid="reschedule"]').click();
await rescheduleLink.click();
await expect(page.getByText("Seats available").first()).toBeVisible();
await page.waitForURL((url) => {
+12 -24
View File
@@ -58,16 +58,14 @@ test.describe("Bookings", () => {
const firstUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(0);
const secondUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(1);
await expect(
firstUpcomingBooking.locator(`text=${bookingWhereFirstUserIsAttendee!.title}`)
).toBeVisible();
await expect(
secondUpcomingBooking.locator(`text=${bookingWhereFirstUserIsOrganizer!.title}`)
).toBeVisible();
});
test("Cannot choose date range presets", async ({ page, users, bookings, webhooks }) => {
test("Cannot choose date range presets", async ({ page, users }) => {
const firstUser = await users.create();
await firstUser.apiLogin();
const bookingsGetResponse = page.waitForResponse((response) =>
@@ -91,7 +89,7 @@ test.describe("Bookings", () => {
test.describe("Past bookings", () => {
test("Mark first guest as no-show", async ({ page, users, bookings, webhooks }) => {
const firstUser = await users.create();
const secondUser = await users.create();
await users.create();
const bookingWhereFirstUserIsOrganizerFixture = await createBooking({
title: "Booking as organizer",
@@ -112,7 +110,6 @@ test.describe("Bookings", () => {
await page.goto(`/bookings/past`);
const pastBookings = page.locator('[data-testid="past-bookings"]');
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
const firstGuest = firstPastBooking.locator('[data-testid="guest"]').nth(0);
await firstGuest.click();
await expect(page.locator('[data-testid="unmark-no-show"]')).toBeHidden();
@@ -142,7 +139,7 @@ test.describe("Bookings", () => {
});
test("Mark 3rd attendee as no-show", async ({ page, users, bookings }) => {
const firstUser = await users.create();
const secondUser = await users.create();
await users.create();
const bookingWhereFirstUserIsOrganizerFixture = await createBooking({
title: "Booking as organizer",
@@ -158,23 +155,22 @@ test.describe("Bookings", () => {
{ name: "Fourth", email: "fourth@cal.com", timeZone: "Europe/Berlin" },
],
});
const bookingWhereFirstUserIsOrganizer = await bookingWhereFirstUserIsOrganizerFixture.self();
await bookingWhereFirstUserIsOrganizerFixture.self();
await firstUser.apiLogin();
await page.goto(`/bookings/past`);
const pastBookings = page.locator('[data-testid="past-bookings"]');
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
const moreGuests = firstPastBooking.locator('[data-testid="more-guests"]');
await moreGuests.click();
const firstGuestInMore = page.getByRole("menuitemcheckbox").nth(0);
await expect(firstGuestInMore).toBeChecked({ checked: false });
await expect(firstGuestInMore).toHaveAttribute("data-state", "unchecked");
await firstGuestInMore.click();
await expect(firstGuestInMore).toBeChecked({ checked: true });
await expect(firstGuestInMore).toHaveAttribute("data-state", "checked");
const updateNoShow = firstPastBooking.locator('[data-testid="update-no-show"]');
await updateNoShow.click();
await moreGuests.click();
await expect(firstGuestInMore).toBeChecked({ checked: true });
await expect(firstGuestInMore).toHaveAttribute("data-state", "checked");
});
test("Team admin/owner can mark first attendee as no-show", async ({
page,
@@ -199,11 +195,10 @@ test.describe("Bookings", () => {
});
const booking = await bookingFixture.self();
await adminUser.apiLogin();
const { webhookReceiver, teamId } = await webhooks.createTeamReceiver();
const { webhookReceiver } = await webhooks.createTeamReceiver();
await page.goto(`/bookings/past`);
const pastBookings = page.locator('[data-testid="past-bookings"]');
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
const firstGuest = firstPastBooking.locator('[data-testid="guest"]').nth(0);
await firstGuest.click();
await expect(page.locator('[data-testid="mark-no-show"]')).toBeVisible();
@@ -232,7 +227,7 @@ test.describe("Bookings", () => {
webhookReceiver.close();
});
test("Can choose date range presets", async ({ page, users, bookings, webhooks }) => {
test("Can choose date range presets", async ({ page, users }) => {
const firstUser = await users.create();
await firstUser.apiLogin();
const bookingsGetResponse = page.waitForResponse((response) =>
@@ -374,22 +369,15 @@ test.describe("Bookings", () => {
//verify with the booking titles
const firstUpcomingBooking = bookingListItems.nth(0);
await expect(
firstUpcomingBooking.locator(`text=${thirdUserOrganizerBooking!.title}`)
).toBeVisible();
await expect(firstUpcomingBooking.locator(`text=${thirdUserOrganizerBooking!.title}`)).toBeVisible();
const secondUpcomingBooking = bookingListItems.nth(1);
await expect(
secondUpcomingBooking.locator(`text=${thirdUserAttendeeIndividualBooking!.title}`)
).toBeVisible();
const thirdUpcomingBooking = bookingListItems.nth(2);
await expect(
thirdUpcomingBooking.locator(`text=${thirdUserAttendeeTeamEvent!.title}`)
).toBeVisible();
await expect(thirdUpcomingBooking.locator(`text=${thirdUserAttendeeTeamEvent!.title}`)).toBeVisible();
});
test("Does not show booking from another user from collective event type when a member is filtered", async ({
@@ -414,7 +402,7 @@ test.describe("Bookings", () => {
const { team } = await owner.getFirstTeamMembership();
const eventType = await owner.getFirstTeamEvent(team.id);
const { id: eventTypeId, title: teamEventTitle, slug: teamEventSlug } = eventType;
const { id: eventTypeId } = eventType;
// remove myself from host of this event type
await prisma.host.delete({
+94 -4
View File
@@ -6,6 +6,7 @@ import { v4 } from "uuid";
import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes";
import stripe from "@calcom/features/ee/payments/server/stripe";
import type { AppFlags } from "@calcom/features/flags/config";
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -27,6 +28,18 @@ export function hashPassword(password: string) {
return hashedPassword;
}
/**
* Default feature flags enabled for all teams and organizations created in E2E tests.
* These flags represent the most common production features that should be tested by default.
*/
const DEFAULT_TEAM_FEATURE_FLAGS: Array<keyof AppFlags> = [];
/**
* Default feature flags enabled for individual users created in E2E tests.
* Empty by default - users don't typically have feature flags unless explicitly needed.
*/
const DEFAULT_USER_FEATURE_FLAGS: Array<keyof AppFlags> = ["bookings-v3"];
type UserFixture = ReturnType<typeof createUserFixture>;
export type CreateUsersFixture = ReturnType<typeof createUsersFixture>;
@@ -155,6 +168,7 @@ const createTeamAndAddUser = async (
schedulingType,
assignAllTeamMembersForSubTeamEvents,
teamSlug,
teamFeatureFlags,
}: {
user: { id: number; email: string; username: string | null; role?: MembershipRole };
isUnpublished?: boolean;
@@ -168,6 +182,7 @@ const createTeamAndAddUser = async (
schedulingType?: SchedulingType;
assignAllTeamMembersForSubTeamEvents?: boolean;
teamSlug?: string;
teamFeatureFlags?: Array<keyof AppFlags>;
},
workerInfo: WorkerInfo
) => {
@@ -196,13 +211,14 @@ const createTeamAndAddUser = async (
data.slug = !isUnpublished ? slug : undefined;
if (isOrg && hasSubteam) {
const team = await createTeamAndAddUser({ user }, workerInfo);
await createTeamEventType(user, team, {
const subteam = await createTeamAndAddUser({ user, teamFeatureFlags }, workerInfo);
await createTeamEventType(user, subteam, {
schedulingType: schedulingType,
assignAllTeamMembers: assignAllTeamMembersForSubTeamEvents,
});
await createTeamWorkflow(user, team);
data.children = { connect: [{ id: team.id }] };
await createTeamWorkflow(user, subteam);
data.children = { connect: [{ id: subteam.id }] };
}
data.orgProfiles = isOrg
? {
@@ -235,6 +251,18 @@ const createTeamAndAddUser = async (
},
});
// Enable feature flags for the team if specified
if (teamFeatureFlags && teamFeatureFlags.length > 0) {
await prisma.teamFeatures.createMany({
data: teamFeatureFlags.map((featureFlag) => ({
teamId: team.id,
featureId: featureFlag,
assignedBy: "e2e-fixture",
assignedAt: new Date(),
})),
});
}
return team;
};
@@ -270,6 +298,23 @@ export const createUsersFixture = (
| (CustomUserOpts & {
organizationId?: number | null;
overrideDefaultEventTypes?: boolean;
/**
* Feature flags to enable for this individual user.
* Defaults to DEFAULT_USER_FEATURE_FLAGS.
* Pass specific flags to enable user-level features.
* @default DEFAULT_USER_FEATURE_FLAGS
* @example
* ```typescript
* // Default feature flags
* const user = await users.create();
*
* // Specific feature flags
* const user = await users.create({
* userFeatureFlags: ["bookings-v3"]
* });
* ```
*/
userFeatureFlags?: Array<keyof AppFlags>;
})
| null,
scenario: {
@@ -295,6 +340,31 @@ export const createUsersFixture = (
orgRequestedSlug?: string;
assignAllTeamMembers?: boolean;
assignAllTeamMembersForSubTeamEvents?: boolean;
/**
* Feature flags to enable for the created team(s) and organization(s).
* Defaults to DEFAULT_TEAM_FEATURE_FLAGS when hasTeam is true.
* Pass an empty array to disable default flags, or pass specific flags to override.
* Applies to both regular teams and organizations (when isOrg: true).
* @default DEFAULT_TEAM_FEATURE_FLAGS
* @example
* ```typescript
* // Default feature flags
* const user = await users.create({}, { hasTeam: true });
*
* // Specific feature flags
* const user = await users.create({}, {
* hasTeam: true,
* teamFeatureFlags: ["pbac"]
* });
*
* // Organizations also get the flags by default
* const user = await users.create({}, {
* hasTeam: true,
* isOrg: true
* }); // Org gets DEFAULT_TEAM_FEATURE_FLAGS
* ```
*/
teamFeatureFlags?: Array<keyof AppFlags>;
} = {}
) => {
const _user = await prisma.user.create({
@@ -347,9 +417,27 @@ export const createUsersFixture = (
where: { id: _user.id },
include: userIncludes,
});
// Enable feature flags for the user if specified
// Default to DEFAULT_USER_FEATURE_FLAGS if not specified
const userFeatureFlags = opts?.userFeatureFlags ?? DEFAULT_USER_FEATURE_FLAGS;
if (userFeatureFlags.length > 0) {
await prisma.userFeatures.createMany({
data: userFeatureFlags.map((featureFlag) => ({
userId: user.id,
featureId: featureFlag,
assignedBy: "e2e-fixture",
assignedAt: new Date(),
})),
});
}
if (scenario.hasTeam) {
const numberOfTeams = scenario.numberOfTeams || 1;
for (let i = 0; i < numberOfTeams; i++) {
// Determine feature flags to use
const featureFlags = scenario.teamFeatureFlags ?? DEFAULT_TEAM_FEATURE_FLAGS;
const team = await createTeamAndAddUser(
{
user: {
@@ -368,10 +456,12 @@ export const createUsersFixture = (
schedulingType: scenario.schedulingType,
assignAllTeamMembersForSubTeamEvents: scenario.assignAllTeamMembersForSubTeamEvents,
teamSlug: scenario?.teamSlug,
teamFeatureFlags: featureFlags,
},
workerInfo
);
store.teams.push(team);
const teamEvent = await createTeamEventType(user, team, scenario);
if (scenario.teammates) {
// Create Teammate users