Files
calendar/apps/web/playwright/insights.e2e.ts
T
Eunjae LeeGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>sean-brydon
0332118724 feat: add e2e tests for insights charts (#23777)
* feat: add e2e tests for insights ChartCard components

- Test that all ChartCard components render with expected titles
- Test graceful handling of TRPC call failures
- Test ChartCard rendering for organization users
- Wait for page load and TRPC calls before checking chart presence
- Use text-based selectors to find ChartCard titles in h2 elements
- Remove page.waitForTimeout() calls to comply with ESLint rules

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* clean up e2e tests

* fix titles

* still render charts if no data

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2025-09-15 11:00:00 +02:00

276 lines
7.9 KiB
TypeScript

import { expect } from "@playwright/test";
import { randomString } from "@calcom/lib/random";
import prisma from "@calcom/prisma";
import { clearFilters, applySelectFilter } from "./filter-helpers";
import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
const createTeamsAndMembership = async (userIdOne: number, userIdTwo: number) => {
const teamOne = await prisma.team.create({
data: {
name: "test-insights",
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
},
});
const teamTwo = await prisma.team.create({
data: {
name: "test-insights-2",
slug: `test-insights-2-${Date.now()}-${randomString(5)}}`,
},
});
if (!userIdOne || !userIdTwo || !teamOne || !teamTwo) {
throw new Error("Failed to create test data");
}
// create memberships
await prisma.membership.create({
data: {
userId: userIdOne,
teamId: teamOne.id,
accepted: true,
role: "ADMIN",
},
});
await prisma.membership.create({
data: {
teamId: teamTwo.id,
userId: userIdOne,
accepted: true,
role: "ADMIN",
},
});
await prisma.membership.create({
data: {
teamId: teamOne.id,
userId: userIdTwo,
accepted: true,
role: "MEMBER",
},
});
await prisma.membership.create({
data: {
teamId: teamTwo.id,
userId: userIdTwo,
accepted: true,
role: "MEMBER",
},
});
return { teamOne, teamTwo };
};
test.afterEach(async ({ users }) => {
await users.deleteAll();
});
test.describe("Insights", async () => {
test("should be able to go to insights as admins", async ({ page, users }) => {
const user = await users.create();
const userTwo = await users.create();
await createTeamsAndMembership(user.id, userTwo.id);
await user.apiLogin();
// go to insights page
await page.goto("/insights");
await page.locator('[data-testid^="insights-filters-false-"]').waitFor();
});
test("should be able to go to insights as members", async ({ page, users }) => {
const user = await users.create();
const userTwo = await users.create();
await userTwo.apiLogin();
await createTeamsAndMembership(user.id, userTwo.id);
// go to insights page
await page.goto("/insights");
await page.locator('[data-testid^="insights-filters-false-"]').waitFor();
});
test("team select filter should have 2 teams and your account option only as member", async ({
page,
users,
}) => {
const user = await users.create();
const userTwo = await users.create();
await user.apiLogin();
await createTeamsAndMembership(user.id, userTwo.id);
// go to insights page
await page.goto("/insights");
await page.locator(`[data-testid=insights-filters-false-undefined-${user.id}]`).waitFor();
await page.getByTestId("dashboard-shell").getByText("Your account").click();
await expect(await page.locator('[data-testid="org-teams-filter-item"]')).toHaveCount(3);
await expect(await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "All" })).toHaveCount(
0
);
await expect(
await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "Your account" })
).toHaveCount(1);
});
test("Insights Organization should have isAll option true", async ({ users, page }) => {
const owner = await users.create(undefined, {
hasTeam: true,
isUnpublished: true,
isOrg: true,
hasSubteam: true,
});
await owner.apiLogin();
await page.goto("/insights");
await page.locator('[data-testid^="insights-filters-true-"]').waitFor();
await page.locator('[data-testid^="insights-filters-true-"]').getByText("All").click();
await expect(await page.locator('[data-testid="org-teams-filter-item"]')).toHaveCount(3);
await expect(
await page
.locator('[data-testid="org-teams-filter-item"]', { hasText: "All" })
.locator('input[type="checkbox"]')
).toBeChecked();
});
test("should not have all option as admin in non-org", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
await page.getByTestId("dashboard-shell").getByText("Your account").click();
await expect(await page.locator('[data-testid="org-teams-filter-item"]')).toHaveCount(3);
await expect(await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "All" })).toHaveCount(
0
);
});
test("should be able to switch between teams and self profile for insights", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
await page.getByTestId("dashboard-shell").getByText("Your account").click();
await expect(await page.locator('[data-testid="org-teams-filter-item"]')).toHaveCount(3);
// switch to self profile
await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "Your account" }).click();
// switch to team 1
await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "test-insights" }).first().click();
// switch to team 2
await page.locator('[data-testid="org-teams-filter-item"]', { hasText: "test-insights-2" }).click();
});
test("should be able to switch between memberUsers", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
// Choose a team
await page.getByTestId("dashboard-shell").getByText("Your account").click();
await page.locator('[data-testid="org-teams-filter-item"]').nth(1).click();
await page.keyboard.press("Escape");
await applySelectFilter(page, "userId", member.username || "");
await clearFilters(page);
await expect(page).not.toHaveURL(/[?&]userId=/);
});
test("should test download button", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
const downloadPromise = page.waitForEvent("download");
// Expect download button to be visible
await expect(page.locator("text=Download")).toBeVisible();
// Click on Download button
await page.getByText("Download").click();
// Expect as csv option to be visible
await expect(page.locator("text=as CSV")).toBeVisible();
// Start waiting for download before clicking. Note no await.
await page.getByText("as CSV").click();
const download = await downloadPromise;
// Wait for the download process to complete and save the downloaded file somewhere.
await download.saveAs("./" + "test-insights.csv");
});
test("should render all ChartCard components with expected titles", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
const expectedChartTitles = [
"Events",
"Performance",
"Event Trends",
"Bookings by Hour",
"Average Event Duration",
"Most Bookings Scheduled",
"Least Bookings Scheduled",
"Most Bookings Completed",
"Least Bookings Completed",
"Most Cancelled",
"Most No-Show",
"Recent No-Show Guests",
"Highest Rated",
"Lowest Rated",
"Recent Ratings",
"Popular Events",
];
for (const title of expectedChartTitles) {
const chartCard = page.locator("[data-testid='panel-card'] h2").filter({ hasText: title });
await expect(chartCard).toBeVisible();
}
});
});