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..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 });
@@ -260,6 +247,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;
+};