import { expect } from "@playwright/test"; import { v4 as uuidv4 } from "uuid"; import { BookingStatus } from "@calcom/prisma/enums"; import { expectAllChartsToLoad, getAllChartIds } from "./lib/chart-helpers"; import { test } from "./lib/fixtures"; test.afterEach(({ users }) => users.deleteAll()); test.describe("Insights > Charts Loading", () => { test.describe("Routing Insights", () => { test("all charts should load successfully with data", async ({ page, users, routingForms, prisma }) => { // Setup: Create routing form with complete data const owner = await users.create( { name: "Chart Test Owner" }, { hasTeam: true, isUnpublished: true, isOrg: true, hasSubteam: true, } ); await owner.apiLogin(); const membership = await owner.getFirstTeamMembership(); // Create a routing form with fields const formName = "Chart Loading Test Form"; const form = await routingForms.create({ name: formName, userId: owner.id, teamId: membership.teamId, fields: [ { type: "text", label: "Name", identifier: "name", required: true, }, { type: "email", label: "Email", identifier: "email", required: true, }, { type: "select", label: "Department", identifier: "department", required: true, options: [ { id: uuidv4(), label: "Sales", }, { id: uuidv4(), label: "Support", }, ], }, ], }); const fieldIds = (form.fields as Array<{ id: string }>).map((f) => f.id); // Create users for routing const user1 = await users.create({ name: "Sales User" }); const user2 = await users.create({ name: "Support User" }); // Add users to the team await prisma.membership.createMany({ data: [ { teamId: membership.teamId, userId: user1.id, role: "MEMBER", accepted: true, }, { teamId: membership.teamId, userId: user2.id, role: "MEMBER", accepted: true, }, ], }); // Create bookings for both users const booking1 = await prisma.booking.create({ data: { uid: `booking-${uuidv4()}`, title: "Sales Booking", startTime: new Date(), endTime: new Date(Date.now() + 60 * 60 * 1000), userId: user1.id, status: BookingStatus.ACCEPTED, }, }); const booking2 = await prisma.booking.create({ data: { uid: `booking-${uuidv4()}`, title: "Support Booking", startTime: new Date(), endTime: new Date(Date.now() + 60 * 60 * 1000), userId: user2.id, status: BookingStatus.ACCEPTED, }, }); // Create form responses with bookings await prisma.app_RoutingForms_FormResponse.create({ data: { formFillerId: "filler-1", formId: form.id, response: { [fieldIds[0]]: { label: "Name", value: "John Doe" }, [fieldIds[1]]: { label: "Email", value: "john@example.com" }, [fieldIds[2]]: { label: "Department", value: "Sales" }, }, routedToBookingUid: booking1.uid, }, }); await prisma.app_RoutingForms_FormResponse.create({ data: { formFillerId: "filler-2", formId: form.id, response: { [fieldIds[0]]: { label: "Name", value: "Jane Smith" }, [fieldIds[1]]: { label: "Email", value: "jane@example.com" }, [fieldIds[2]]: { label: "Department", value: "Support" }, }, routedToBookingUid: booking2.uid, }, }); // Create a response without booking (for "Failed Bookings" chart) await prisma.app_RoutingForms_FormResponse.create({ data: { formFillerId: "filler-3", formId: form.id, response: { [fieldIds[0]]: { label: "Name", value: "Bob Wilson" }, [fieldIds[1]]: { label: "Email", value: "bob@example.com" }, [fieldIds[2]]: { label: "Department", value: "Sales" }, }, routedToBookingUid: null, }, }); // Navigate to insights routing page await page.goto("/insights/routing"); await expect(page).toHaveURL(/\/insights\/routing/); // Wait for the page to be fully loaded await page.waitForLoadState("networkidle"); // Get all chart IDs before checking const chartIds = await getAllChartIds(page); console.log(`[Routing] Found ${chartIds.length} charts: ${chartIds.join(", ")}`); // Assert all charts load successfully await expectAllChartsToLoad(page, 20000); // 20 second timeout for all charts // Verify specific expected charts are present and loaded const expectedCharts = ["stats", "routing-funnel", "routed-to-per-period", "failed-bookings-by-field"]; for (const chartId of expectedCharts) { const chart = page.locator(`[data-testid="chart-card"][data-chart-id="${chartId}"]`); await expect(chart).toBeVisible(); await expect(chart).toHaveAttribute("data-loading-state", "loaded"); } // Cleanup await prisma.booking.delete({ where: { id: booking1.id } }); await prisma.booking.delete({ where: { id: booking2.id } }); }); }); test.describe("Bookings Insights", () => { test("all charts should load successfully with booking data", async ({ page, users, bookings, prisma, }) => { // Setup: Create users with team membership const owner = await users.create({ name: "Chart Test Owner" }); const member = await users.create({ name: "Chart Test Member" }); // Create team and add both users const team = await prisma.team.create({ data: { name: "test-insights-bookings", slug: `test-insights-bookings-${Date.now()}`, }, }); await prisma.membership.createMany({ data: [ { userId: owner.id, teamId: team.id, accepted: true, role: "ADMIN", }, { userId: member.id, teamId: team.id, accepted: true, role: "MEMBER", }, ], }); await owner.apiLogin(); // Create some bookings for the user const now = new Date(); const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const dayAfterTomorrow = new Date(tomorrow); dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1); const threeDaysFromNow = new Date(dayAfterTomorrow); threeDaysFromNow.setDate(threeDaysFromNow.getDate() + 1); // Create bookings at different times to ensure unique idempotency keys await bookings.create(owner.id, owner.username, owner.eventTypes[0].id, { status: BookingStatus.ACCEPTED, startTime: now, endTime: tomorrow, }); await bookings.create(owner.id, owner.username, owner.eventTypes[0].id, { status: BookingStatus.ACCEPTED, startTime: tomorrow, endTime: dayAfterTomorrow, }); await bookings.create(owner.id, owner.username, owner.eventTypes[0].id, { status: BookingStatus.CANCELLED, startTime: dayAfterTomorrow, endTime: threeDaysFromNow, }); // Navigate to insights bookings page await page.goto("/insights"); await expect(page).toHaveURL(/\/insights/); // Wait for the page to be fully loaded await page.waitForLoadState("networkidle"); // Get all chart IDs before checking const chartIds = await getAllChartIds(page); console.log(`[Bookings] Found ${chartIds.length} charts: ${chartIds.join(", ")}`); // Assert all charts load successfully await expectAllChartsToLoad(page, 20000); // 20 second timeout for all charts // Verify specific expected charts are present and loaded const expectedCharts = [ "events", "performance", "event-trends", "no-show-hosts-over-time", "csat-over-time", "bookings-by-hour", "average-event-duration", ]; for (const chartId of expectedCharts) { const chart = page.locator(`[data-testid="chart-card"][data-chart-id="${chartId}"]`); // Chart should exist and be loaded await expect(chart).toHaveCount(1); await expect(chart).toHaveAttribute("data-loading-state", "loaded"); } // Verify no charts are in error state const errorCharts = page.locator('[data-testid="chart-card"][data-loading-state="error"]'); await expect(errorCharts).toHaveCount(0, { timeout: 0 }); }); }); });