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 <keithwillcode@gmail.com>
This commit is contained in:
Benny JooandKeith Williams authored and GitHub committed 2025-06-20 23:00:39 +05:30
1 parent 866a0e2596
commit 5dd4937a1a
9 files changed
+106 -44

No files matched your search

+5 -5
View File
@@ -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}`);
@@ -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);
});
});
@@ -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);
},
};
}
+31 -4
View File
@@ -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 };
}
@@ -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(
@@ -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<ReturnType<typeof setupOrgMember>>;
interface ToggleSeoSwitchParams {
@@ -168,7 +168,9 @@ export const EventMeta = ({
{translatedTitle ?? event?.title}
</EventTitle>
{(event.description || translatedDescription) && (
<EventMetaBlock contentClassName="mb-8 break-words max-w-full max-h-[180px] scroll-bar pr-4">
<EventMetaBlock
data-testid="event-meta-description"
contentClassName="mb-8 break-words max-w-full max-h-[180px] scroll-bar pr-4">
<div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
@@ -43,16 +43,14 @@ type EventDetailCustomBlock = {
type EventDetailsProps = EventDetailsPropsBase & (EventDetailDefaultBlock | EventDetailCustomBlock);
interface EventMetaProps {
interface EventMetaProps extends React.HTMLAttributes<HTMLDivElement> {
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 ? (
<img
src={iconUrl}
@@ -170,6 +170,7 @@ export const EventSetupTab = (
}}
disabled={!orgId}
tooltip={!orgId ? t("orgs_upgrade_to_enable_feature") : undefined}
data-testid="ai_translation_toggle"
/>
</div>
)}