From d1ff8333d2e4bee7aba2a225ca3c8a9c160430c6 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 22 Aug 2025 15:44:43 +0300 Subject: [PATCH 1/3] chore(e2e): E2e tests for trials --- .changeset/thick-onions-strive.md | 5 ++ integration/tests/pricing-table.test.ts | 62 +++++++++++++++++++ .../unstable/page-objects/checkout.ts | 2 +- .../unstable/page-objects/pricingTable.ts | 6 +- 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 .changeset/thick-onions-strive.md diff --git a/.changeset/thick-onions-strive.md b/.changeset/thick-onions-strive.md new file mode 100644 index 00000000000..f49611c6e92 --- /dev/null +++ b/.changeset/thick-onions-strive.md @@ -0,0 +1,5 @@ +--- +'@clerk/testing': patch +--- + +Update click events to support labels related to trials. diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 32e81dfa5a8..2cae84ef3be 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -62,6 +62,18 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await u.po.signIn.waitForMounted(); await expect(u.po.page.getByText('Checkout')).toBeHidden(); }); + + test('when signed out, clicking trial plan redirects to sign in', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.page.goToRelative('/pricing-table'); + + await u.po.pricingTable.waitForMounted(); + await expect(u.po.page.getByText(/Start \d+-day free trial/i)).toBeVisible(); + await u.po.pricingTable.startCheckout({ planSlug: 'trial' }); + + await u.po.signIn.waitForMounted(); + await expect(u.po.page.getByText('Checkout')).toBeHidden(); + }); }); test.describe('when signed in flow', () => { @@ -243,6 +255,56 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await newFakeUser.deleteIfExists(); }); + test('starts free trial subscription for new user', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + // Create a new user specifically for this trial test + const trialUser = u.services.users.createFakeUser(); + await u.services.users.createBapiUser(trialUser); + + try { + // Sign in the new user + await u.po.signIn.goTo(); + await u.po.signIn.signInWithEmailAndInstantPassword({ + email: trialUser.email, + password: trialUser.password, + }); + + // Navigate to pricing table + await u.po.page.goToRelative('/pricing-table'); + await u.po.pricingTable.waitForMounted(); + + // Verify trial plan is displayed with trial CTA + // Note: This assumes there's a plan with trial enabled in the test environment + // The button text should show "Start [X]-day free trial" for trial-enabled plans + await expect(u.po.page.getByText(/Start \d+-day free trial/i)).toBeVisible(); + + // Start checkout for a trial plan (assuming 'pro' has trial enabled in test env) + await u.po.pricingTable.startCheckout({ planSlug: 'trial' }); + await u.po.checkout.waitForMounted(); + + // Verify checkout shows trial details + await expect(u.po.checkout.root.getByText('Checkout')).toBeVisible(); + await expect(u.po.checkout.root.getByText('Free trial')).toBeVisible(); + await expect(u.po.checkout.root.getByText('Total Due after')).toBeVisible(); + + await u.po.checkout.fillTestCard(); + await u.po.checkout.clickPayOrSubscribe(); + + await expect(u.po.checkout.root.getByText(/Trial.*successfully.*started/i)).toBeVisible(); + await u.po.checkout.confirmAndContinue(); + + // Verify the user is now shown as having an active free trial + // The pricing table should show their current plan as active + await u.po.pricingTable.waitToBeActive({ planSlug: 'trial' }); + // Verify trial end date is displayed + await expect(u.po.page.getByText(/Trial ends/i)).toBeVisible(); + } finally { + // Clean up the trial user + await trialUser.deleteIfExists(); + } + }); + test.describe('in UserProfile', () => { // test.describe.configure({ mode: 'serial' }); test('renders pricing table, subscribes to a plan, revalidates payment sources on complete and then downgrades to free', async ({ diff --git a/packages/testing/src/playwright/unstable/page-objects/checkout.ts b/packages/testing/src/playwright/unstable/page-objects/checkout.ts index 42a021722dc..6e30931c653 100644 --- a/packages/testing/src/playwright/unstable/page-objects/checkout.ts +++ b/packages/testing/src/playwright/unstable/page-objects/checkout.ts @@ -32,7 +32,7 @@ export const createCheckoutPageObject = (testArgs: { page: EnhancedPage }) => { return page.frameLocator('iframe[src*="elements-inner-payment"]').getByLabel('Card number').waitFor({ state }); }, clickPayOrSubscribe: async () => { - await self.root.getByRole('button', { name: /subscribe|pay\s\$/i }).click(); + await self.root.getByRole('button', { name: /subscribe|pay\s\$|start/i }).click(); }, waitForSubscribeButton: async () => { await self.root.getByRole('button', { name: /^subscribe$/i }).waitFor({ state: 'visible' }); diff --git a/packages/testing/src/playwright/unstable/page-objects/pricingTable.ts b/packages/testing/src/playwright/unstable/page-objects/pricingTable.ts index 113cc714db4..2b9fe75fba5 100644 --- a/packages/testing/src/playwright/unstable/page-objects/pricingTable.ts +++ b/packages/testing/src/playwright/unstable/page-objects/pricingTable.ts @@ -74,7 +74,11 @@ export const createPricingTablePageObject = (testArgs: { page: EnhancedPage }) = period?: BillingPeriod; }) => { const targetButtonName = - shouldSwitch === true ? 'Switch to this plan' : shouldSwitch === false ? /subscribe/i : /get|switch|subscribe/i; + shouldSwitch === true + ? 'Switch to this plan' + : shouldSwitch === false + ? /subscribe/i + : /get|switch|subscribe|Start \d+-day free trial/i; if (period) { await ensurePricingPeriod(planSlug, period); From 7d105821552fdad9f205d61fb01175ddac48e47a Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 22 Aug 2025 16:00:46 +0300 Subject: [PATCH 2/3] adding more steps --- integration/tests/pricing-table.test.ts | 39 ++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 2cae84ef3be..e94e387deb7 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -297,8 +297,45 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl // Verify the user is now shown as having an active free trial // The pricing table should show their current plan as active await u.po.pricingTable.waitToBeActive({ planSlug: 'trial' }); - // Verify trial end date is displayed + + await u.po.page.goToRelative('/user'); + await u.po.userProfile.waitForMounted(); + await u.po.userProfile.switchToBillingTab(); + + await expect( + u.po.page + .locator('.cl-profileSectionContent__subscriptionsList') + .getByText(/Trial/i) + .locator('xpath=..') + .getByText(/Free trial/i), + ).toBeVisible(); + await expect(u.po.page.getByText(/Trial ends/i)).toBeVisible(); + + await u.po.page.getByRole('button', { name: 'Manage subscription' }).first().click(); + await u.po.subscriptionDetails.waitForMounted(); + await u.po.subscriptionDetails.root.locator('.cl-menuButtonEllipsisBordered').click(); + await u.po.subscriptionDetails.root.getByText('Cancel free trial').click(); + await u.po.subscriptionDetails.root.locator('.cl-drawerConfirmationRoot').waitFor({ state: 'visible' }); + await u.po.subscriptionDetails.root.getByRole('button', { name: 'Cancel free trial' }).click(); + await u.po.subscriptionDetails.waitForUnmounted(); + + await expect( + u.po.page + .locator('.cl-profileSectionContent__subscriptionsList') + .getByText(/Trial/i) + .locator('xpath=..') + .getByText(/Free trial/i), + ).toBeVisible(); + + // Verify the Free plan with Upcoming status exists + await expect( + u.po.page + .locator('.cl-profileSectionContent__subscriptionsList') + .getByText('Free') + .locator('xpath=..') + .getByText('Upcoming'), + ).toBeVisible(); } finally { // Clean up the trial user await trialUser.deleteIfExists(); From 3aaf59bd3709ee31278a837aec6b466e6b93bdd5 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 25 Aug 2025 16:06:05 +0300 Subject: [PATCH 3/3] fix astro and vue --- integration/tests/pricing-table.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index e94e387deb7..7ff5c9f690c 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -294,6 +294,9 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await expect(u.po.checkout.root.getByText(/Trial.*successfully.*started/i)).toBeVisible(); await u.po.checkout.confirmAndContinue(); + await u.po.page.goToRelative('/pricing-table'); + await u.po.pricingTable.waitForMounted(); + // Verify the user is now shown as having an active free trial // The pricing table should show their current plan as active await u.po.pricingTable.waitToBeActive({ planSlug: 'trial' });