From 5dd4937a1ad34a650a7a73089f9658c31495620d Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Fri, 20 Jun 2025 13:30:39 -0400 Subject: [PATCH] chore: add e2e test for ai translation (#18061) * make createNewEventType receive description arg * remove @test tag * wip * wip2 * remove @test tag * modify EventMetaProps * add test id event-meta-description * improve test * better name * try * finalized * fix * fix type check * address comment * cleann up * cleann up * fix * Update ai-translation.e2e.ts * fix * simplify --------- Co-authored-by: Keith Williams --- apps/web/playwright/event-types.e2e.ts | 10 ++--- .../eventType/ai-translation.e2e.ts | 45 +++++++++++++++++++ .../playwright/fixtures/regularBookings.ts | 14 ++++++ apps/web/playwright/lib/testUtils.ts | 35 +++++++++++++-- .../manage-booking-questions.e2e.ts | 4 +- .../organization/organization-settings.e2e.ts | 29 +----------- .../bookings/Booker/components/EventMeta.tsx | 4 +- .../components/event-meta/Details.tsx | 8 ++-- .../components/tabs/setup/EventSetupTab.tsx | 1 + 9 files changed, 106 insertions(+), 44 deletions(-) create mode 100644 apps/web/playwright/eventType/ai-translation.e2e.ts diff --git a/apps/web/playwright/event-types.e2e.ts b/apps/web/playwright/event-types.e2e.ts index c11b8a04c5..a6f278abcc 100644 --- a/apps/web/playwright/event-types.e2e.ts +++ b/apps/web/playwright/event-types.e2e.ts @@ -7,7 +7,7 @@ import { randomString } from "@calcom/lib/random"; import { test } from "./lib/fixtures"; import { bookTimeSlot, - createNewEventType, + createNewUserEventType, gotoBookingPage, gotoFirstEventType, saveEventType, @@ -55,7 +55,7 @@ test.describe("Event Types tests", () => { test("can add new event type", async ({ page }) => { const nonce = randomString(3); const eventTitle = `hello ${nonce}`; - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); await page.goto("/event-types"); await expect(page.locator(`text='${eventTitle}'`)).toBeVisible(); }); @@ -63,7 +63,7 @@ test.describe("Event Types tests", () => { test("new event type appears first in the list", async ({ page }) => { const nonce = randomString(3); const eventTitle = `hello ${nonce}`; - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); await page.goto("/event-types"); const firstEvent = page.locator("[data-testid=event-types] > li a").first(); const firstEventTitle = await firstEvent.getAttribute("title"); @@ -73,7 +73,7 @@ test.describe("Event Types tests", () => { test("enabling recurring event comes with default options", async ({ page }) => { const nonce = randomString(3); const eventTitle = `my recurring event ${nonce}`; - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); // fix the race condition await page.waitForSelector('[data-testid="event-title"]'); @@ -393,7 +393,7 @@ test.describe("Event Types tests", () => { }) => { const nonce = randomString(3); const eventTitle = `Conflict event ${nonce}`; - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); await page.goto("/event-types"); await page.click(`text=${eventTitle}`); diff --git a/apps/web/playwright/eventType/ai-translation.e2e.ts b/apps/web/playwright/eventType/ai-translation.e2e.ts new file mode 100644 index 0000000000..99c01f9e82 --- /dev/null +++ b/apps/web/playwright/eventType/ai-translation.e2e.ts @@ -0,0 +1,45 @@ +import { expect } from "@playwright/test"; +import { loginUser } from "playwright/fixtures/regularBookings"; + +import { MembershipRole } from "@calcom/prisma/enums"; + +import { test } from "../lib/fixtures"; + +test.describe.configure({ mode: "parallel" }); + +test.describe("AI Translation - Event Type", () => { + test("Toggle should not be clickable for free plan members", async ({ page, users, bookingPage }) => { + await loginUser(users); + await page.goto("/event-types"); + await bookingPage.goToEventType("30 min"); + expect(await bookingPage.getAITranslationToggleDisabled()).toBe(true); + }); + + test("Toggle should not be clickable for team plan members", async ({ page, users, bookingPage }) => { + const user = await users.create( + { name: "testuser" }, + { + hasTeam: true, + teamRole: MembershipRole.MEMBER, + } + ); + await user.apiLogin(); + await page.goto("/event-types"); + await bookingPage.goToEventType("30 min"); + expect(await bookingPage.getAITranslationToggleDisabled()).toBe(true); + }); + + test("Toggle should be clickable for org members", async ({ page, orgs, users, bookingPage }) => { + const org = await orgs.create({ + name: "acme", + }); + const user = await users.create({ + organizationId: org.id, + roleInOrganization: MembershipRole.MEMBER, + }); + await user.apiLogin(); + await page.goto("/event-types"); + await bookingPage.goToEventType("30 min"); + expect(await bookingPage.getAITranslationToggleDisabled()).toBe(false); + }); +}); diff --git a/apps/web/playwright/fixtures/regularBookings.ts b/apps/web/playwright/fixtures/regularBookings.ts index 001224e158..7e9af1652a 100644 --- a/apps/web/playwright/fixtures/regularBookings.ts +++ b/apps/web/playwright/fixtures/regularBookings.ts @@ -198,5 +198,19 @@ export function createBookingPageFixture(page: Page) { checkTimeSlotsCount: async (eventTypePage: Page, count: number) => { await expect(eventTypePage.getByTestId("time")).toHaveCount(count); }, + getAITranslationToggleDisabled: async () => { + await page.waitForSelector('[data-testid="ai_translation_toggle"]', { + timeout: 5000, + state: "attached", + }); + const toggle = page.getByTestId("ai_translation_toggle"); + return (await toggle.getAttribute("disabled")) !== null; + }, + toggleAITranslation: async () => { + await page.getByTestId("ai_translation_toggle").click(); + }, + updateEventTypeDescription: async (description: string) => { + await page.getByTestId("editor-input").fill(description); + }, }; } diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 3bd1458722..237e95ab87 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -7,12 +7,14 @@ import { createServer } from "http"; // eslint-disable-next-line no-restricted-imports import type { Messages } from "mailhog"; import { totp } from "otplib"; +import { v4 as uuid } from "uuid"; import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus, SchedulingType } from "@calcom/prisma/enums"; import type { createEmailsFixture } from "../fixtures/emails"; +import type { CreateUsersFixture } from "../fixtures/users"; import type { Fixtures } from "./fixtures"; type Request = IncomingMessage & { body?: unknown }; @@ -177,10 +179,12 @@ export async function expectSlotNotAllowedToBook(page: Page) { await expect(page.locator("[data-testid=slot-not-allowed-to-book]")).toBeVisible(); } -export const createNewEventType = async (page: Page, args: { eventTitle: string }) => { +export const createNewUserEventType = async (page: Page, args: { eventTitle: string; username?: string }) => { await page.click("[data-testid=new-event-type]"); - const eventTitle = args.eventTitle; - await page.fill("[name=title]", eventTitle); + if (args.username) { + await page.getByRole("button", { name: args.username }).click(); + } + await page.fill("[name=title]", args.eventTitle); await page.fill("[name=length]", "10"); await page.click("[type=submit]"); @@ -216,7 +220,7 @@ export async function setupManagedEvent({ export const createNewSeatedEventType = async (page: Page, args: { eventTitle: string }) => { const eventTitle = args.eventTitle; - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); await page.waitForSelector('[data-testid="event-title"]'); await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute( "aria-current", @@ -563,3 +567,26 @@ export async function expectPageToBeNotFound({ page, url }: { page: Page; url: s await page.goto(`${url}`); await expect(page.getByTestId(`404-page`)).toBeVisible(); } + +export async function setupOrgMember(users: CreateUsersFixture) { + const orgRequestedSlug = `example-${uuid()}`; + + const orgMember = await users.create(undefined, { + hasTeam: true, + isOrg: true, + hasSubteam: true, + isOrgVerified: true, + isDnsSetup: true, + orgRequestedSlug, + schedulingType: SchedulingType.ROUND_ROBIN, + }); + + const { team: org } = await orgMember.getOrgMembership(); + const { team } = await orgMember.getFirstTeamMembership(); + const teamEvent = await orgMember.getFirstTeamEvent(team.id); + const userEvent = orgMember.eventTypes[0]; + + await orgMember.apiLogin(); + + return { orgMember, org, team, teamEvent, userEvent }; +} diff --git a/apps/web/playwright/manage-booking-questions.e2e.ts b/apps/web/playwright/manage-booking-questions.e2e.ts index 08da60940f..faf89ea052 100644 --- a/apps/web/playwright/manage-booking-questions.e2e.ts +++ b/apps/web/playwright/manage-booking-questions.e2e.ts @@ -12,7 +12,7 @@ import type { CalendarEvent } from "@calcom/types/Calendar"; import { test } from "./lib/fixtures"; import { createHttpServer, - createNewEventType, + createNewUserEventType, selectFirstAvailableTimeSlotNextMonth, submitAndWaitForResponse, } from "./lib/testUtils"; @@ -752,7 +752,7 @@ test.describe("Text area min and max characters text", () => { // We wait until loading is finished await page.waitForSelector('[data-testid="event-types"]'); - await createNewEventType(page, { eventTitle }); + await createNewUserEventType(page, { eventTitle }); await page.waitForSelector('[data-testid="event-title"]'); await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toContainText("Event Setup"); //fix the race condition await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute( diff --git a/apps/web/playwright/organization/organization-settings.e2e.ts b/apps/web/playwright/organization/organization-settings.e2e.ts index ebd3b9de84..8b2aa084ab 100644 --- a/apps/web/playwright/organization/organization-settings.e2e.ts +++ b/apps/web/playwright/organization/organization-settings.e2e.ts @@ -1,36 +1,9 @@ import { expect } from "@playwright/test"; import type { Page } from "@playwright/test"; -import { doOnOrgDomain } from "playwright/lib/testUtils"; -import { v4 as uuid } from "uuid"; +import { doOnOrgDomain, setupOrgMember } from "playwright/lib/testUtils"; -import { SchedulingType } from "@calcom/prisma/enums"; - -import type { CreateUsersFixture } from "../fixtures/users"; import { test } from "../lib/fixtures"; -async function setupOrgMember(users: CreateUsersFixture) { - const orgRequestedSlug = `example-${uuid()}`; - - const orgMember = await users.create(undefined, { - hasTeam: true, - isOrg: true, - hasSubteam: true, - isOrgVerified: true, - isDnsSetup: true, - orgRequestedSlug, - schedulingType: SchedulingType.ROUND_ROBIN, - }); - - const { team: org } = await orgMember.getOrgMembership(); - const { team } = await orgMember.getFirstTeamMembership(); - const teamEvent = await orgMember.getFirstTeamEvent(team.id); - const userEvent = orgMember.eventTypes[0]; - - await orgMember.apiLogin(); - - return { orgMember, org, team, teamEvent, userEvent }; -} - type TestContext = Awaited>; interface ToggleSeoSwitchParams { diff --git a/packages/features/bookings/Booker/components/EventMeta.tsx b/packages/features/bookings/Booker/components/EventMeta.tsx index f76a9a8f8c..07affdc549 100644 --- a/packages/features/bookings/Booker/components/EventMeta.tsx +++ b/packages/features/bookings/Booker/components/EventMeta.tsx @@ -168,7 +168,9 @@ export const EventMeta = ({ {translatedTitle ?? event?.title} {(event.description || translatedDescription) && ( - +
{ customIcon?: React.ReactNode; icon?: IconName; iconUrl?: string; - children: React.ReactNode; // Emphasises the text in the block. For now only // applying in dark mode. highlight?: boolean; contentClassName?: string; - className?: string; isDark?: boolean; } @@ -80,6 +78,7 @@ export const EventMetaBlock = ({ contentClassName, className, isDark, + ...rest }: EventMetaProps) => { if (!React.Children.count(children)) return null; @@ -89,7 +88,8 @@ export const EventMetaBlock = ({ "flex items-start justify-start text-sm", highlight ? "text-emphasis" : "text-text", className - )}> + )} + {...rest}> {iconUrl ? (
)}