|
| 1 | +import type { Locator } from '@playwright/test'; |
1 | 2 | import { expect, test } from '@playwright/test'; |
2 | 3 |
|
3 | 4 | import { appConfigs } from '../presets'; |
@@ -278,14 +279,22 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl |
278 | 279 | // Verify checkout shows trial details |
279 | 280 | await expect(u.po.checkout.root.getByText('Checkout')).toBeVisible(); |
280 | 281 | await expect(u.po.checkout.root.getByText('Free trial')).toBeVisible(); |
281 | | - await expect(u.po.checkout.root.getByText('Total Due after')).toBeVisible(); |
| 282 | + const title = /^Total Due after trial ends in \d+ days$/i; |
| 283 | + await expect(matchLineItem(u.po.checkout.root, title, '$999.00')).toBeVisible(); |
282 | 284 |
|
283 | 285 | await u.po.checkout.fillTestCard(); |
284 | 286 | await u.po.checkout.clickPayOrSubscribe(); |
285 | 287 |
|
286 | 288 | await expect(u.po.checkout.root.getByText(/Trial.*successfully.*started/i)).toBeVisible({ |
287 | 289 | timeout: 15_000, |
288 | 290 | }); |
| 291 | + |
| 292 | + const footer = u.po.checkout.root.locator('.cl-drawerFooter'); |
| 293 | + await expect(matchLineItem(footer, 'Total paid', '$0.00')).toBeVisible(); |
| 294 | + await expect(matchLineItem(footer, 'Trial ends on')).toBeVisible(); |
| 295 | + await expect(matchLineItem(footer, 'Payment method', 'Visa ⋯ 4242')).toBeVisible(); |
| 296 | + expect(await countLineItems(footer)).toBe(3); |
| 297 | + |
289 | 298 | await u.po.checkout.confirmAndContinue(); |
290 | 299 |
|
291 | 300 | await u.po.page.goToRelative('/pricing-table'); |
@@ -344,12 +353,18 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl |
344 | 353 | // Verify checkout shows trial details |
345 | 354 | await expect(u.po.checkout.root.getByText('Checkout')).toBeVisible(); |
346 | 355 | await expect(u.po.checkout.root.getByText('Free trial')).toBeHidden(); |
347 | | - await expect(u.po.checkout.root.getByText('Total Due after')).toBeHidden(); |
348 | | - await expect(u.po.checkout.root.getByText('Total Due Today')).toBeVisible(); |
| 356 | + |
| 357 | + await expect(matchLineItem(u.po.checkout.root, 'Total Due after')).toBeHidden(); |
| 358 | + await expect(matchLineItem(u.po.checkout.root, 'Subtotal', '$999.00')).toBeVisible(); |
| 359 | + await expect(matchLineItem(u.po.checkout.root, 'Total Due Today', '$999.00')).toBeVisible(); |
| 360 | + expect(await countLineItems(u.po.checkout.root)).toBe(3); |
349 | 361 |
|
350 | 362 | await u.po.checkout.root.getByRole('button', { name: /^pay\s\$/i }).waitFor({ state: 'visible' }); |
351 | 363 | await u.po.checkout.clickPayOrSubscribe(); |
352 | | - await expect(u.po.page.getByText('Payment was successful!')).toBeVisible(); |
| 364 | + await expect(u.po.checkout.root.getByText('Payment was successful!')).toBeVisible(); |
| 365 | + await expect(matchLineItem(footer, 'Total paid', '$999.00')).toBeVisible(); |
| 366 | + await expect(matchLineItem(footer, 'Payment method', 'Visa ⋯ 4242')).toBeVisible(); |
| 367 | + expect(await countLineItems(footer)).toBe(2); |
353 | 368 | await u.po.checkout.confirmAndContinue(); |
354 | 369 |
|
355 | 370 | await u.po.page.goToRelative('/user'); |
@@ -877,3 +892,34 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl |
877 | 892 | }); |
878 | 893 | }); |
879 | 894 | }); |
| 895 | + |
| 896 | +/** |
| 897 | + * Helper to match a line item by its title and optionally its description. |
| 898 | + * Line items are rendered as Clerk LineItems components with element descriptors: |
| 899 | + * - .cl-lineItemsTitle contains the title |
| 900 | + * - .cl-lineItemsDescription contains the description (immediately following the title) |
| 901 | + */ |
| 902 | +function matchLineItem(root: Locator, title: string | RegExp, description?: string | RegExp): Locator { |
| 903 | + // Find the title element using the Clerk-generated class |
| 904 | + const titleElement = root.locator('.cl-lineItemsTitle').filter({ hasText: title }); |
| 905 | + |
| 906 | + // If no description is provided, return the title element |
| 907 | + if (description === undefined) { |
| 908 | + return titleElement; |
| 909 | + } |
| 910 | + |
| 911 | + // Get the next sibling description element using the Clerk-generated class |
| 912 | + const descriptionElement = titleElement |
| 913 | + .locator('xpath=following-sibling::*[1][contains(@class, "cl-lineItemsDescription")]') |
| 914 | + .filter({ hasText: description }); |
| 915 | + |
| 916 | + return descriptionElement; |
| 917 | +} |
| 918 | + |
| 919 | +/** |
| 920 | + * Helper to count the number of line items within a given root element. |
| 921 | + * Line items are rendered as Clerk LineItems components where each .cl-lineItemsTitle represents a line item. |
| 922 | + */ |
| 923 | +async function countLineItems(root: Locator): Promise<number> { |
| 924 | + return await root.locator('.cl-lineItemsTitle').count(); |
| 925 | +} |
0 commit comments