From 087fa03513b41ed9234b5b6017e99ee6592a7b59 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 22 Jan 2026 12:09:13 +0100 Subject: [PATCH] test: add e2e test for Feature Opt-In banner (#27100) ## What does this PR do? Adds an end-to-end test for the Feature Opt-In banner flow. This test verifies the complete user journey from seeing the banner to enabling a feature. Currently `OPT_IN_FEATURES` in `packages/features/feature-opt-in/config.ts` is empty, so this e2e test skips. Once we uncomment the `bookings-v3` item from the config, this e2e test will run and pass. This is a preparation for when we enable the feature. **Changes:** - Creates a new e2e test file (`feature-opt-in-banner.e2e.ts`) that tests the complete opt-in flow - Adds `data-testid` attributes to banner and dialog components for reliable test selectors ## Mandatory Tasks (DO NOT REMOVE) - [x] I have self-reviewed the code (A decent size PR without self-review might be rejected). - [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). N/A - test only. - [x] I confirm automated tests are in place that prove my fix is effective or that my feature works. ## How should this be tested? 1. Add the `ready-for-e2e` label to run e2e tests in CI 2. The test should pass when run with `PLAYWRIGHT_HEADLESS=1 yarn e2e feature-opt-in-banner.e2e.ts` **Test requirements:** - The `bookings-v3` feature flag must exist in the Features table (the test uses `prisma.feature.upsert` to ensure this) - The test will skip if `bookings-v3` is not in `OPT_IN_FEATURES` ## Checklist - [x] I have read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md) - [x] My code follows the style guidelines of this project - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have checked if my changes generate no new warnings ## Updates since last revision - Replaced text-based locators (`getByText`, `getByRole`) with `data-testid` selectors for more reliable element targeting - Added `data-testid` attributes to `FeatureOptInBanner`, `FeatureOptInConfirmDialog`, and `FeatureOptInSuccessDialog` components ## Human Review Checklist - [ ] Verify the `data-testid` attributes don't conflict with existing test IDs in the codebase - [ ] Confirm the test correctly skips when `bookings-v3` is not in `OPT_IN_FEATURES` - [ ] Check that the added `data-testid` attributes follow the project's naming conventions --- Link to Devin run: https://app.devin.ai/sessions/97158ab1b7414e3988ba803b30d95b3e Requested by: @eunjae-lee --- .../playwright/feature-opt-in-banner.e2e.ts | 64 +++++++++++++++++++ .../components/FeatureOptInBanner.tsx | 15 ++++- .../components/FeatureOptInConfirmDialog.tsx | 11 +++- .../components/FeatureOptInSuccessDialog.tsx | 10 ++- 4 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 apps/web/playwright/feature-opt-in-banner.e2e.ts diff --git a/apps/web/playwright/feature-opt-in-banner.e2e.ts b/apps/web/playwright/feature-opt-in-banner.e2e.ts new file mode 100644 index 0000000000..624b405c29 --- /dev/null +++ b/apps/web/playwright/feature-opt-in-banner.e2e.ts @@ -0,0 +1,64 @@ +import { OPT_IN_FEATURES } from "@calcom/features/feature-opt-in/config"; +import { expect } from "@playwright/test"; +import { test } from "./lib/fixtures"; + +test.afterEach(({ users }) => users.deleteAll()); + +test.describe("Feature Opt-In Banner", () => { + test("shows banner on bookings page and allows user to opt-in to bookings-v3 feature", async ({ + page, + users, + prisma, + }) => { + if (!OPT_IN_FEATURES.some((item) => item.slug === "bookings-v3")) { + return; + } + + // Enable the bookings-v3 feature flag globally in the database + // This is required for the banner to show (globalEnabled must be true) + // Use upsert to ensure the feature exists and is enabled + await prisma.feature.upsert({ + where: { slug: "bookings-v3" }, + update: { enabled: true }, + create: { slug: "bookings-v3", enabled: true, type: "OPERATIONAL" }, + }); + + // Create a user WITHOUT the bookings-v3 feature flag enabled + // By passing an empty array, we override the default user feature flags + const user = await users.create({ + userFeatureFlags: [], + }); + + await user.apiLogin(); + + // Navigate to the bookings page + await page.goto("/bookings/upcoming"); + + // Wait for the banner to appear + const banner = page.getByTestId("feature-opt-in-banner"); + await expect(banner).toBeVisible({ timeout: 15000 }); + + // Click the "Try it" button to open the confirmation dialog + const tryItButton = page.getByTestId("feature-opt-in-banner-try-it"); + await tryItButton.click(); + + // Verify the confirmation dialog appears + const confirmDialog = page.getByTestId("feature-opt-in-confirm-dialog"); + await expect(confirmDialog).toBeVisible(); + + // Click the "Enable" button to opt-in + const enableButton = page.getByTestId("feature-opt-in-confirm-dialog-enable"); + await enableButton.click(); + + // Verify the success dialog appears + const successDialogTitle = page.getByTestId("feature-opt-in-success-dialog-title"); + await expect(successDialogTitle).toBeVisible(); + + // Click "View Settings" button to navigate to the settings page + const viewSettingsButton = page.getByTestId("feature-opt-in-success-dialog-view-settings"); + await viewSettingsButton.click(); + + // Verify we are redirected to the features settings page + await expect(page).toHaveURL(/\/settings\/my-account\/features/); + }); +}); diff --git a/packages/features/feature-opt-in/components/FeatureOptInBanner.tsx b/packages/features/feature-opt-in/components/FeatureOptInBanner.tsx index b24e371d3c..32ebfc5e48 100644 --- a/packages/features/feature-opt-in/components/FeatureOptInBanner.tsx +++ b/packages/features/feature-opt-in/components/FeatureOptInBanner.tsx @@ -20,9 +20,13 @@ export function FeatureOptInBanner({ const { t } = useLocale(); return ( -
+
-

{t(featureConfig.i18n.title)}

+

+ {t(featureConfig.i18n.title)} +

{t(featureConfig.i18n.description)}

-
diff --git a/packages/features/feature-opt-in/components/FeatureOptInConfirmDialog.tsx b/packages/features/feature-opt-in/components/FeatureOptInConfirmDialog.tsx index 8b080426fc..2aa245e358 100644 --- a/packages/features/feature-opt-in/components/FeatureOptInConfirmDialog.tsx +++ b/packages/features/feature-opt-in/components/FeatureOptInConfirmDialog.tsx @@ -214,9 +214,11 @@ export function FeatureOptInConfirmDialog({ return ( - + - {t(featureConfig.i18n.name)} + + {t(featureConfig.i18n.name)} + {t(featureConfig.i18n.description)} @@ -290,7 +292,10 @@ export function FeatureOptInConfirmDialog({ } onClick={resetAndClose} disabled={isSubmitting}> {t("cancel")} - diff --git a/packages/features/feature-opt-in/components/FeatureOptInSuccessDialog.tsx b/packages/features/feature-opt-in/components/FeatureOptInSuccessDialog.tsx index 593ee7d074..db04621b59 100644 --- a/packages/features/feature-opt-in/components/FeatureOptInSuccessDialog.tsx +++ b/packages/features/feature-opt-in/components/FeatureOptInSuccessDialog.tsx @@ -30,16 +30,20 @@ export function FeatureOptInSuccessDialog({ return ( - + - {t("feature_enabled_successfully")} + + {t("feature_enabled_successfully")} + {t("feature_enabled_description")} } onClick={onDismiss}> {t("dismiss")} - +