486817a688
This fixes hanging E2E tests where assertions like toHaveCount(0) and
not.toBeVisible() would wait for the full timeout (10s in CI, 120s locally)
before failing, instead of failing immediately.
These assertions are 'state check' assertions that verify an element is
already absent or hidden, rather than waiting for it to become so.
Adding { timeout: 0 } makes them fail immediately if the condition is not met.
Files updated:
- locale.e2e.ts: 16 instances of toHaveCount(0)
- booking-seats.e2e.ts: 8 instances of toHaveCount(0)
- organization-redirection.e2e.ts: 3 instances of toHaveCount(0)
- organization-creation-flows.e2e.ts: 5 instances of not.toBeVisible()
- insights-charts.e2e.ts: 1 instance of toHaveCount(0)
- bookings-list.e2e.ts: 1 instance of toHaveCount(0)
- availability.e2e.ts: 1 instance of toHaveCount(0)
- managed-event-types.e2e.ts: 1 instance of toHaveCount(0)
- team-invitation.e2e.ts: 1 instance of toHaveCount(0)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
289 lines
9.0 KiB
TypeScript
289 lines
9.0 KiB
TypeScript
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 });
|
|
});
|
|
});
|
|
});
|