Files
calendar/apps/web/playwright/payment-apps.e2e.ts
T
Alex van AndelGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
455a91935d fix: enable alby app in E2E tests to fix payment-apps test failures (#27071)
PR #27012 introduced logic to disable apps without valid keys during seeding.
This caused the alby payment app tests to fail because the alby app was
disabled in the App table when its environment keys weren't configured.

This fix adds an ensureAppEnabled helper function that enables the app
in the App table before running the tests, treating it as a mock/test
setup step.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-21 08:40:31 +05:30

404 lines
14 KiB
TypeScript

import prisma from "@calcom/prisma";
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { selectFirstAvailableTimeSlotNextMonth, submitAndWaitForResponse } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());
async function goToAppsTab(page: Page, eventTypeId?: number): Promise<void> {
await page.goto(`event-types/${eventTypeId}?tabName=apps`);
await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page");
}
/**
* Ensures an app is enabled in the App table and returns a cleanup function.
* This is needed because apps without valid keys are now disabled during seeding.
* For E2E tests, we need to enable the app to test the UI behavior.
* The cleanup function restores the original enabled state to avoid side-effects.
*/
async function ensureAppEnabled(appSlug: string): Promise<() => Promise<void>> {
const app = await prisma.app.findUnique({
where: { slug: appSlug },
select: { enabled: true },
});
const originalEnabled = app?.enabled ?? false;
await prisma.app.update({
where: { slug: appSlug },
data: { enabled: true },
});
return async () => {
await prisma.app.update({
where: { slug: appSlug },
data: { enabled: originalEnabled },
});
};
}
// biome-ignore lint/complexity/noExcessiveLinesPerFunction: E2E test suites naturally have many test cases
test.describe("Payment app", () => {
test("Should be able to edit alby price, currency", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
// Ensure alby app is enabled (it may be disabled if keys are not configured)
const cleanupAlbyApp = await ensureAppEnabled("alby");
try {
await prisma.credential.create({
data: {
type: "alby_payment",
appId: "alby",
userId: user.id,
key: {
account_id: "random",
account_email: "random@example.com",
webhook_endpoint_id: "ep_randomString",
webhook_endpoint_secret: "whsec_randomString",
account_lightning_address: "random@getalby.com",
},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
await page.getByPlaceholder("Price").click();
await page.getByPlaceholder("Price").fill("200");
await page.getByText("SatoshissatsCurrencyBTCPayment optionCollect payment on booking").click();
await submitAndWaitForResponse(page, "/api/trpc/eventTypesHeavy/update?batch=1", {
action: () => page.locator("[data-testid=update-eventtype]").click(),
});
await page.goto(`${user.username}/${paymentEvent?.slug}`);
// expect 200 sats to be displayed in page
await expect(page.locator("text=200 sats").first()).toBeVisible();
await selectFirstAvailableTimeSlotNextMonth(page);
await expect(page.locator("text=200 sats").first()).toBeVisible();
// go to /event-types and check if the price is 200 sats
await page.goto(`event-types/`);
await expect(page.locator("text=200 sats").first()).toBeVisible();
} finally {
await cleanupAlbyApp();
}
});
test("Should be able to edit stripe price, currency", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
await prisma.credential.create({
data: {
type: "stripe_payment",
appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
livemode: false,
token_type: "bearer",
access_token: "sk_test_randomString",
refresh_token: "rt_randomString",
stripe_user_id: "acct_randomString",
default_currency: "usd",
stripe_publishable_key: "pk_test_randomString",
},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
await page.getByTestId("stripe-currency-select").click();
await page.getByTestId("select-option-usd").click();
await page.getByTestId("stripe-price-input").click();
await page.getByTestId("stripe-price-input").fill("350");
await page.getByTestId("update-eventtype").click();
await page.goto(`${user.username}/${paymentEvent?.slug}`);
// expect 200 sats to be displayed in page
expect(await page.locator("text=350").first()).toBeTruthy();
await selectFirstAvailableTimeSlotNextMonth(page);
expect(await page.locator("text=350").first()).toBeTruthy();
// go to /event-types and check if the price is 200 sats
await page.goto(`event-types/`);
expect(await page.locator("text=350").first()).toBeTruthy();
});
test("Should be able to edit paypal price, currency", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
await prisma.credential.create({
data: {
type: "paypal_payment",
appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
secret_key: "randomString",
webhook_id: "randomString",
},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
await page.getByPlaceholder("Price").click();
await page.getByPlaceholder("Price").fill("150");
await page.getByTestId("paypal-currency-select").click();
await page.getByTestId("select-option-MXN").click();
await page.getByTestId("paypal-payment-option-select").click();
await page.getByText("$MXNCurrencyMexican pesoPayment option").click();
await page.getByTestId("update-eventtype").click();
await page.goto(`${user.username}/${paymentEvent?.slug}`);
// expect 150 to be displayed in page
expect(await page.locator("text=MX$150.00").first()).toBeTruthy();
await selectFirstAvailableTimeSlotNextMonth(page);
// expect 150 to be displayed in page
expect(await page.locator("text=MX$150.00").first()).toBeTruthy();
// go to /event-types and check if the price is 150
await page.goto(`event-types/`);
expect(await page.locator("text=MX$150.00").first()).toBeTruthy();
});
test("Should display App is not setup already for alby", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
// Ensure alby app is enabled (it may be disabled if keys are not configured)
const cleanupAlbyApp = await ensureAppEnabled("alby");
try {
await prisma.credential.create({
data: {
type: "alby_payment",
appId: "alby",
userId: user.id,
key: {},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
// expect text "This app has not been setup yet" to be displayed
expect(await page.locator("text=This app has not been setup yet").first()).toBeTruthy();
await page.getByRole("button", { name: "Setup" }).click();
// Expect "Connect with Alby" to be displayed
expect(await page.locator("text=Connect with Alby").first()).toBeTruthy();
} finally {
await cleanupAlbyApp();
}
});
test("Should display App is not setup already for paypal", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
await prisma.credential.create({
data: {
type: "paypal_payment",
appId: "paypal",
userId: user.id,
key: {},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
// expect text "This app has not been setup yet" to be displayed
expect(await page.locator("text=This app has not been setup yet").first()).toBeTruthy();
await page.getByRole("button", { name: "Setup" }).click();
// Expect "Getting started with Paypal APP" to be displayed
expect(await page.locator("text=Getting started with Paypal APP").first()).toBeTruthy();
});
/**
* For now almost all the payment apps show display "This app has not been setup yet"
* this can change in the future
*/
test("Should not display App is not setup already for non payment app", async ({ page, users }) => {
// We will use google analytics app for this test
const user = await users.create();
await user.apiLogin();
// Any event should work here
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
expect(paymentEvent).not.toBeNull();
await prisma.credential.create({
data: {
type: "ga4_analytics",
userId: user.id,
appId: "ga4",
invalid: false,
key: {},
},
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("#event-type-form").getByRole("switch").click();
// make sure Tracking ID is displayed
expect(await page.locator("text=Tracking ID").first()).toBeTruthy();
await page.getByLabel("Tracking ID").click();
await page.getByLabel("Tracking ID").fill("demo");
await page.getByTestId("update-eventtype").click();
});
test("Should only be allowed to enable one payment app", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
if (!paymentEvent) {
throw new Error("No payment event found");
}
await prisma.credential.createMany({
data: [
{
type: "paypal_payment",
appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
secret_key: "randomString",
webhook_id: "randomString",
},
},
{
type: "stripe_payment",
appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
livemode: false,
token_type: "bearer",
access_token: "sk_test_randomString",
refresh_token: "rt_randomString",
stripe_user_id: "acct_randomString",
default_currency: "usd",
stripe_publishable_key: "pk_test_randomString",
},
},
],
});
await goToAppsTab(page, paymentEvent?.id);
await page.locator("[data-testid='paypal-app-switch']").click();
await page.locator("[data-testid='stripe-app-switch']").isDisabled();
});
test("when more than one payment app is installed the price should be updated when changing settings", async ({
page,
users,
}) => {
const user = await users.create();
await user.apiLogin();
const paymentEvent = user.eventTypes.find((item) => item.slug === "paid");
if (!paymentEvent) {
throw new Error("No payment event found");
}
await prisma.credential.createMany({
data: [
{
type: "paypal_payment",
appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
secret_key: "randomString",
webhook_id: "randomString",
},
},
{
type: "stripe_payment",
appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
livemode: false,
token_type: "bearer",
access_token: "sk_test_randomString",
refresh_token: "rt_randomString",
stripe_user_id: "acct_randomString",
default_currency: "usd",
stripe_publishable_key: "pk_test_randomString",
},
},
],
});
await goToAppsTab(page, paymentEvent?.id);
await page.getByTestId("paypal-app-switch").click();
await page.getByTestId("paypal-price-input").fill("100");
await page.getByTestId("paypal-currency-select").first().click();
await page.getByTestId("select-option-MXN").click();
// await page.locator(".mb-1 > .bg-default > div > div:nth-child(2)").first().click();
// await page.getByText("$MXNCurrencyMexican pesoPayment option").click();
await page.getByTestId("update-eventtype").click();
// Need to wait for the DB to be updated
await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200);
const paypalPrice = await prisma.eventType.findFirst({
where: {
id: paymentEvent.id,
},
select: {
price: true,
},
});
expect(paypalPrice?.price).toEqual(10000);
await page.getByTestId("paypal-app-switch").click();
await page.getByTestId("stripe-app-switch").click();
await page.getByTestId("stripe-price-input").fill("200");
await page.getByTestId("update-eventtype").click();
// Need to wait for the DB to be updated
await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200);
const stripePrice = await prisma.eventType.findFirst({
where: {
id: paymentEvent.id,
},
select: {
price: true,
},
});
expect(stripePrice?.price).toEqual(20000);
});
});