mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
test: deepen billing dialog E2E tests per review feedback
- Cancel dialog: mock /customers/billing route, click button, assert API call via expect.poll; add error handling test - TopUp dialog: assert pay input updates on preset click, use aria-label for close button, verify pricing link href and target - Add aria-label to TopUpCredits close buttons for semantic locators - Extract shared helpers to reduce duplication
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { WorkspaceStore } from '@e2e/types/globals'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
async function openCancelSubscriptionDialog(page: Page, cancelAt?: string) {
|
||||
await page.evaluate((date) => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showCancelSubscriptionDialog(date)
|
||||
}, cancelAt)
|
||||
}
|
||||
|
||||
test.describe('CancelSubscription dialog', { tag: '@ui' }, () => {
|
||||
test('displays dialog with title and description', async ({ comfyPage }) => {
|
||||
test('displays dialog with title and formatted date', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showCancelSubscriptionDialog('2025-12-31T12:00:00Z')
|
||||
})
|
||||
await openCancelSubscriptionDialog(page, '2025-12-31T12:00:00Z')
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
@@ -25,11 +32,7 @@ test.describe('CancelSubscription dialog', { tag: '@ui' }, () => {
|
||||
test('"Keep subscription" button closes dialog', async ({ comfyPage }) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showCancelSubscriptionDialog()
|
||||
})
|
||||
await openCancelSubscriptionDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
@@ -41,11 +44,7 @@ test.describe('CancelSubscription dialog', { tag: '@ui' }, () => {
|
||||
test('Escape key closes dialog', async ({ comfyPage }) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showCancelSubscriptionDialog()
|
||||
})
|
||||
await openCancelSubscriptionDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
@@ -54,22 +53,77 @@ test.describe('CancelSubscription dialog', { tag: '@ui' }, () => {
|
||||
await expect(dialog).toBeHidden()
|
||||
})
|
||||
|
||||
test('"Cancel subscription" button is clickable', async ({ comfyPage }) => {
|
||||
test('"Cancel subscription" button triggers cancel API request', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showCancelSubscriptionDialog()
|
||||
let cancelCalled = false
|
||||
|
||||
// Mock the legacy billing portal endpoint (POST /customers/billing)
|
||||
await page.route('**/customers/billing', (route) => {
|
||||
cancelCalled = true
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
billing_portal_url: 'https://billing.example.com/portal'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Mock the subscription status endpoint
|
||||
await page.route('**/customers/cloud-subscription-status', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
plan_name: 'pro',
|
||||
is_active: true,
|
||||
cancel_at_period_end: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Prevent window.open from actually opening a new tab
|
||||
await page.evaluate(() => {
|
||||
window.open = () => null
|
||||
})
|
||||
|
||||
await openCancelSubscriptionDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const cancelBtn = dialog.getByRole('button', {
|
||||
name: 'Cancel subscription'
|
||||
})
|
||||
await expect(cancelBtn).toBeVisible()
|
||||
await expect(cancelBtn).toBeEnabled()
|
||||
await cancelBtn.click()
|
||||
|
||||
await expect.poll(() => cancelCalled).toBe(true)
|
||||
await expect(dialog).toBeHidden()
|
||||
})
|
||||
|
||||
test('shows error toast when cancel API fails', async ({ comfyPage }) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
// Mock the legacy billing portal endpoint to fail
|
||||
await page.route('**/customers/billing', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ message: 'Internal server error' })
|
||||
})
|
||||
})
|
||||
|
||||
await openCancelSubscriptionDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
await dialog.getByRole('button', { name: 'Cancel subscription' }).click()
|
||||
|
||||
// Dialog should remain visible on error
|
||||
await expect(dialog).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { WorkspaceStore } from '@e2e/types/globals'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
async function openTopUpCreditsDialog(
|
||||
page: Page,
|
||||
options?: { isInsufficientCredits?: boolean }
|
||||
) {
|
||||
await page.evaluate((opts) => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog(opts)
|
||||
}, options)
|
||||
}
|
||||
|
||||
test.describe('TopUpCredits dialog', { tag: '@ui' }, () => {
|
||||
test('displays dialog with heading and preset amounts', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog()
|
||||
})
|
||||
await openTopUpCreditsDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
@@ -41,11 +49,7 @@ test.describe('TopUpCredits dialog', { tag: '@ui' }, () => {
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog({ isInsufficientCredits: true })
|
||||
})
|
||||
await openTopUpCreditsDialog(page, { isInsufficientCredits: true })
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
@@ -58,59 +62,52 @@ test.describe('TopUpCredits dialog', { tag: '@ui' }, () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('selecting a preset amount updates the selection', async ({
|
||||
test('selecting a preset amount updates the pay amount', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog()
|
||||
})
|
||||
await openTopUpCreditsDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
await dialog.getByRole('button', { name: '$10' }).first().click()
|
||||
// Default preset is $50, click $10 instead
|
||||
const tenDollarBtn = dialog.getByRole('button', { name: '$10' }).first()
|
||||
await tenDollarBtn.click()
|
||||
|
||||
await expect(dialog).toContainText('Credits')
|
||||
// The "You Pay" input should reflect $10
|
||||
const payInput = dialog.locator('input').first()
|
||||
await expect(payInput).toHaveValue('10')
|
||||
})
|
||||
|
||||
test('close button dismisses dialog', async ({ comfyPage }) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog()
|
||||
})
|
||||
await openTopUpCreditsDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const closeButton = dialog.getByRole('button').filter({
|
||||
has: page.locator('i.icon-\\[lucide--x\\]')
|
||||
})
|
||||
await closeButton.click()
|
||||
|
||||
await dialog.getByRole('button', { name: 'Close' }).click()
|
||||
await expect(dialog).toBeHidden()
|
||||
})
|
||||
|
||||
test('shows pricing details link', async ({ comfyPage }) => {
|
||||
test('pricing details link points to docs pricing page', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { page } = comfyPage
|
||||
|
||||
await page.evaluate(() => {
|
||||
void (
|
||||
window.app!.extensionManager as WorkspaceStore
|
||||
).dialog.showTopUpCreditsDialog()
|
||||
})
|
||||
await openTopUpCreditsDialog(page)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
await expect(
|
||||
dialog.getByRole('link', { name: 'View pricing details' })
|
||||
).toBeVisible()
|
||||
const pricingLink = dialog.getByRole('link', {
|
||||
name: 'View pricing details'
|
||||
})
|
||||
await expect(pricingLink).toBeVisible()
|
||||
await expect(pricingLink).toHaveAttribute('href', /partner-nodes\/pricing/)
|
||||
await expect(pricingLink).toHaveAttribute('target', '_blank')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</h2>
|
||||
<button
|
||||
class="focus-visible:ring-secondary-foreground cursor-pointer rounded-sm border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none"
|
||||
:aria-label="$t('g.close')"
|
||||
@click="() => handleClose()"
|
||||
>
|
||||
<i class="icon-[lucide--x] size-6" />
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</h2>
|
||||
<button
|
||||
class="focus-visible:ring-secondary-foreground cursor-pointer rounded-sm border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:ring-1 focus-visible:outline-none"
|
||||
:aria-label="$t('g.close')"
|
||||
@click="() => handleClose()"
|
||||
>
|
||||
<i class="icon-[lucide--x] size-6" />
|
||||
|
||||
Reference in New Issue
Block a user