From c8ab03e4ece5fe9376d3c2d68725b15aab57f5f5 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 16 Jul 2025 18:02:28 +0300 Subject: [PATCH 1/2] chore(clerk-js,e2e): Test case for cancelling a plan --- .changeset/lovely-ghosts-fall.md | 5 +++ .changeset/slow-zoos-work.md | 5 +++ integration/tests/pricing-table.test.ts | 45 +++++++++++++++++++ .../components/SubscriptionDetails/index.tsx | 21 +++++---- .../playwright/unstable/page-objects/index.ts | 2 + .../page-objects/subscriptionDetails.ts | 20 +++++++++ 6 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 .changeset/lovely-ghosts-fall.md create mode 100644 .changeset/slow-zoos-work.md create mode 100644 packages/testing/src/playwright/unstable/page-objects/subscriptionDetails.ts diff --git a/.changeset/lovely-ghosts-fall.md b/.changeset/lovely-ghosts-fall.md new file mode 100644 index 00000000000..214068c5390 --- /dev/null +++ b/.changeset/lovely-ghosts-fall.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Marking root of subscription modal with `cl-subscriptionDetails-root`. diff --git a/.changeset/slow-zoos-work.md b/.changeset/slow-zoos-work.md new file mode 100644 index 00000000000..bdac0c616dd --- /dev/null +++ b/.changeset/slow-zoos-work.md @@ -0,0 +1,5 @@ +--- +'@clerk/testing': patch +--- + +Adding subscription details page object. diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index bf804a71ff6..87a05354a5a 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -260,6 +260,51 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await fakeUser.deleteIfExists(); }); + test('can unsubscribe from a plan', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + const fakeUser = u.services.users.createFakeUser(); + await u.services.users.createBapiUser(fakeUser); + + await u.po.signIn.goTo(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.page.goToRelative('/user'); + + await u.po.userProfile.waitForMounted(); + await u.po.userProfile.switchToBillingTab(); + await expect(u.po.page.getByText(/Free/i)).toBeVisible(); + await u.po.page.getByRole('button', { name: 'Switch plans' }).click(); + await u.po.pricingTable.startCheckout({ planSlug: 'plus' }); + await u.po.checkout.waitForMounted(); + await u.po.checkout.fillTestCard(); + await u.po.checkout.clickPayOrSubscribe(); + await expect(u.po.page.getByText('Payment was successful!')).toBeVisible(); + + await u.po.checkout.confirmAndContinue(); + await u.po.page.locator('.cl-headerBackLink').getByText('Plans').click(); + + await u.page.waitForTimeout(1000); + await expect(u.po.page.locator('.cl-profileSectionContent__subscriptionsList').getByText('Plus')).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 subscription').click(); + await u.po.subscriptionDetails.root.locator('.cl-drawerConfirmationRoot').waitFor({ state: 'visible' }); + await u.po.subscriptionDetails.root.getByText('Cancel subscription').click(); + await u.po.subscriptionDetails.waitForUnmounted(); + + // Verify the Free plan with Upcoming status exists + await expect( + u.po.page + .locator('.cl-profileSectionContent__subscriptionsList') + .getByText('Free') + .locator('xpath=..') + .getByText('Upcoming'), + ).toBeVisible(); + + await fakeUser.deleteIfExists(); + }); + test('checkout always revalidates on open', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx index 669001e7058..c0e38527509 100644 --- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx +++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx @@ -32,6 +32,7 @@ import { Col, descriptors, Flex, + Flow, Heading, localizationKeys, Spinner, @@ -40,7 +41,7 @@ import { } from '../../customizables'; import { SubscriptionBadge } from '../Subscriptions/badge'; -// We cannot derive the state of confrimation modal from the existance subscription, as it will make the animation laggy when the confimation closes. +// We cannot derive the state of confirmation modal from the existence subscription, as it will make the animation laggy when the confimation closes. const SubscriptionForCancellationContext = React.createContext<{ subscription: CommerceSubscriptionResource | null; setSubscription: (subscription: CommerceSubscriptionResource | null) => void; @@ -55,13 +56,17 @@ const SubscriptionForCancellationContext = React.createContext<{ export const SubscriptionDetails = (props: __internal_SubscriptionDetailsProps) => { return ( - - - - - - - + + + + + + + + + + + ); }; diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts index 9f38a003798..8b408d10548 100644 --- a/packages/testing/src/playwright/unstable/page-objects/index.ts +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -12,6 +12,7 @@ import { createPricingTablePageObject } from './pricingTable'; import { createSessionTaskComponentPageObject } from './sessionTask'; import { createSignInComponentPageObject } from './signIn'; import { createSignUpComponentPageObject } from './signUp'; +import { createSubscriptionDetailsPageObject } from './subscriptionDetails'; import { createTestingTokenPageObject } from './testingToken'; import { createUserButtonPageObject } from './userButton'; import { createUserProfileComponentPageObject } from './userProfile'; @@ -48,5 +49,6 @@ export const createPageObjects = ({ userVerification: createUserVerificationComponentPageObject(testArgs), waitlist: createWaitlistComponentPageObject(testArgs), apiKeys: createAPIKeysComponentPageObject(testArgs), + subscriptionDetails: createSubscriptionDetailsPageObject(testArgs), }; }; diff --git a/packages/testing/src/playwright/unstable/page-objects/subscriptionDetails.ts b/packages/testing/src/playwright/unstable/page-objects/subscriptionDetails.ts new file mode 100644 index 00000000000..fb34fe3500c --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/subscriptionDetails.ts @@ -0,0 +1,20 @@ +import type { EnhancedPage } from './app'; +import { common } from './common'; + +export const createSubscriptionDetailsPageObject = (testArgs: { page: EnhancedPage }) => { + const { page } = testArgs; + const self = { + ...common(testArgs), + waitForMounted: (selector = '.cl-subscriptionDetails-root') => { + return page.waitForSelector(selector, { state: 'attached' }); + }, + waitForUnmounted: () => { + return self.root.locator('.cl-drawerRoot').waitFor({ state: 'detached' }); + }, + closeDrawer: () => { + return self.root.locator('.cl-drawerClose').click(); + }, + root: page.locator('.cl-subscriptionDetails-root'), + }; + return self; +}; From d59fa79206211731765a40fd4044dcff04c96021 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 16 Jul 2025 18:06:51 +0300 Subject: [PATCH 2/2] remove comments --- integration/tests/pricing-table.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 87a05354a5a..7199603cec5 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -142,19 +142,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await newFakeUser.deleteIfExists(); }); - // test('can manage and cancel subscription', async ({ page, context }) => { - // const u = createTestUtils({ app, page, context }); - // await u.po.signIn.goTo(); - // await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); - // await u.po.page.goToRelative('/pricing-table'); - - // await u.po.pricingTable.waitForMounted(); - // await u.po.pricingTable.clickManageSubscription(); - // await u.po.page.getByRole('button', { name: 'Cancel subscription' }).click(); - // await u.po.page.getByRole('alertdialog').getByRole('button', { name: 'Cancel subscription' }).click(); - // await expect(u.po.page.getByRole('button', { name: /resubscribe|re-subscribe/i }).first()).toBeVisible(); - // }); - test.describe('redirects', () => { test('default navigates to afterSignInUrl', async ({ page, context }) => { const u = createTestUtils({ app, page, context });