Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import MCR from 'monocart-coverage-reports'
import { COVERAGE_OUTPUT_DIR } from '@e2e/coverageConfig'
import { TOURS, TOUR_SEEN_SETTING } from '@/platform/onboarding/onboardingTours'
import { NodeBadgeMode } from '@/types/nodeSource'
import {
EMPTY_BILLING_BALANCE,
EMPTY_BILLING_PLANS,
LEGACY_PERSONAL_BILLING_STATUS
} from '@e2e/fixtures/data/cloudWorkspace'
import { ZERO_BALANCE } from '@e2e/fixtures/data/subscriptionFixtures'
import { ComfyActionbar } from '@e2e/fixtures/components/Actionbar'
import { ComfyTemplates } from '@e2e/fixtures/components/Templates'
import { ComfyMouse } from '@e2e/fixtures/ComfyMouse'
import { TestIds } from '@e2e/fixtures/selectors'
import { comfyExpect } from '@e2e/fixtures/utils/customMatchers'
import { assetPath } from '@e2e/fixtures/utils/paths'
import { nextFrame, sleep } from '@e2e/fixtures/utils/timing'
import { mockWorkspace, workspace } from '@e2e/fixtures/utils/workspaceMocks'
import { VueNodeHelpers } from '@e2e/fixtures/VueNodeHelpers'
import { BottomPanel } from '@e2e/fixtures/components/BottomPanel'
import { ComfyNodeSearchBox } from '@e2e/fixtures/components/ComfyNodeSearchBox'
Expand Down Expand Up @@ -561,6 +568,23 @@ export const comfyPageFixture = base.extend<{
}

if (testInfo.tags.includes('@cloud')) {
const context = page.context()
await context.route('**/api/auth/session', (route) =>
route.fulfill({ status: 204 })
)
await context.route('**/api/billing/status', (route) =>
route.fulfill({ json: LEGACY_PERSONAL_BILLING_STATUS })
)
await context.route('**/api/billing/balance', (route) =>
route.fulfill({ json: EMPTY_BILLING_BALANCE })
)
await context.route('**/api/billing/plans', (route) =>
route.fulfill({ json: EMPTY_BILLING_PLANS })
)
await context.route('**/customers/balance', (route) =>
route.fulfill({ json: ZERO_BALANCE })
)
await mockWorkspace(context, workspace('personal', 'owner'), [])
await comfyPage.cloudAuth.mockAuth()
}

Expand Down
33 changes: 26 additions & 7 deletions browser_tests/fixtures/data/cloudWorkspace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { BillingStatusResponse as IngestBillingStatusResponse } from '@comfyorg/ingest-types'
import type {
BillingBalanceResponse,
BillingPlansResponse,
BillingStatusResponse as IngestBillingStatusResponse
} from '@comfyorg/ingest-types'

import type {
Member,
Expand All @@ -7,12 +11,27 @@ import type {
} from '@/platform/workspace/api/workspaceApi'
import type { RemoteConfig } from '@/platform/remoteConfig/types'

// `/api/features` is the remote-config source: production builds resolve the
// workspaces flag from it (the `ff:` localStorage override is dev-only).
export const WORKSPACE_FEATURE_FLAG: RemoteConfig = {
team_workspaces_enabled: true,
consolidated_billing_enabled: true
}
export const CLOUD_REMOTE_CONFIG: RemoteConfig = {}

export const LEGACY_PERSONAL_BILLING_STATUS = {
billing_rail: 'legacy_stripe',
billing_status: 'inactive',
has_funds: true,
is_active: false,
subscription_status: 'ended',
subscription_tier: 'FREE',
team_credit_stop: null
} satisfies IngestBillingStatusResponse

export const EMPTY_BILLING_BALANCE = {
amount_micros: 0,
currency: 'usd',
effective_balance_micros: 0
} satisfies BillingBalanceResponse

export const EMPTY_BILLING_PLANS = {
plans: []
} satisfies BillingPlansResponse

export const TEAM_WORKSPACE: WorkspaceWithRole = {
id: 'ws-team',
Expand Down
6 changes: 3 additions & 3 deletions browser_tests/fixtures/helpers/CloudWorkspaceMockHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import type {

import { mockSystemStats } from '@e2e/fixtures/data/systemStats'
import {
CLOUD_REMOTE_CONFIG,
DEFAULT_TEAM_MEMBERS,
TEAM_BILLING_STATUS,
TEAM_PRO_PLAN,
TEAM_WORKSPACE,
WORKSPACE_FEATURE_FLAG
TEAM_WORKSPACE
} from '@e2e/fixtures/data/cloudWorkspace'
import { CloudAuthHelper } from '@e2e/fixtures/helpers/CloudAuthHelper'
import { mockWorkspaceTokenMint } from '@e2e/fixtures/utils/workspaceMocks'
Expand Down Expand Up @@ -71,7 +71,7 @@ export class CloudWorkspaceMockHelper {
const { page } = this

await page.route('**/api/features', (r) =>
r.fulfill(jsonRoute(WORKSPACE_FEATURE_FLAG))
r.fulfill(jsonRoute(CLOUD_REMOTE_CONFIG))
)
await page.route('**/api/system_stats', (r) =>
r.fulfill(jsonRoute(mockSystemStats))
Expand Down
40 changes: 31 additions & 9 deletions browser_tests/fixtures/helpers/SubscriptionHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { expect } from '@playwright/test'
import type { Page, Route } from '@playwright/test'

import { PENDING_SUBSCRIPTION_CHECKOUT_STORAGE_KEY } from '@/platform/cloud/subscription/utils/subscriptionCheckoutTracker'
import {
PENDING_SUBSCRIPTION_CHECKOUT_EVENT,
PENDING_SUBSCRIPTION_CHECKOUT_STORAGE_KEY
} from '@/platform/cloud/subscription/utils/subscriptionCheckoutTracker'
import type { BillingStatusResponse } from '@/platform/workspace/api/workspaceApi'
import {
createBalance,
createSubscriptionStatus,
Expand Down Expand Up @@ -39,6 +43,24 @@ function withSubscriptionStatus(
})
}

function toBillingStatus(
status: SubscriptionStatusResponse
): BillingStatusResponse {
return {
is_active: status.is_active ?? false,
has_funds: status.has_fund ?? false,
billing_rail: 'legacy_stripe',
...(status.subscription_tier
? { subscription_tier: status.subscription_tier }
: {}),
...(status.subscription_duration
? { subscription_duration: status.subscription_duration }
: {}),
...(status.renewal_date ? { renewal_date: status.renewal_date } : {}),
...(status.end_date ? { cancel_at: status.end_date } : {})
}
}

export function withActiveSubscription(
tier: NonNullable<SubscriptionStatusResponse['subscription_tier']> = 'CREATOR'
): SubscriptionOperator {
Expand Down Expand Up @@ -104,9 +126,9 @@ export class SubscriptionHelper {
})
await this.page.route(featuresPattern, featuresHandler)

const statusPattern = '**/customers/cloud-subscription-status'
const statusPattern = '**/api/billing/status'
const statusHandler = async (route: Route) => {
await route.fulfill({ json: this.statusResponse })
await route.fulfill({ json: toBillingStatus(this.statusResponse) })
}
this.routeHandlers.push({ pattern: statusPattern, handler: statusHandler })
await this.page.route(statusPattern, statusHandler)
Expand Down Expand Up @@ -179,14 +201,14 @@ export class SubscriptionHelper {
}

/**
* Dispatch `visibilitychange` to simulate returning from Stripe checkout.
* The app re-fetches subscription status when a pending checkout attempt
* exists in localStorage (seeded via `seedPendingCheckout`).
* Notify the app that a pending checkout attempt needs to be recovered.
*/
async triggerSubscriptionRefetch(): Promise<void> {
await this.page.evaluate(() => {
document.dispatchEvent(new Event('visibilitychange'))
})
const eventName = PENDING_SUBSCRIPTION_CHECKOUT_EVENT
await this.page.evaluate(
(name) => window.dispatchEvent(new Event(name)),
eventName
)
}

/**
Expand Down
3 changes: 0 additions & 3 deletions browser_tests/fixtures/utils/cloudBillingMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ export async function mockBilling(page: Page) {
await page.route('**/api/billing/plans', (r) =>
r.fulfill(jsonRoute({ plans: [] }))
)
await page.route('**/customers/cloud-subscription-status', (r) =>
r.fulfill(jsonRoute({ is_active: false }))
)
await page.route('**/customers/balance', (r) =>
r.fulfill(jsonRoute({ amount_micros: 0, currency: 'usd' }))
)
Expand Down
2 changes: 2 additions & 0 deletions browser_tests/fixtures/utils/cloudBootMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { RemoteConfig } from '@/platform/remoteConfig/types'
import { mockSystemStats } from '@e2e/fixtures/data/systemStats'
import { CloudAuthHelper } from '@e2e/fixtures/helpers/CloudAuthHelper'
import { jsonRoute } from '@e2e/fixtures/utils/jsonRoute'
import { mockWorkspace, workspace } from '@e2e/fixtures/utils/workspaceMocks'

interface CloudBootOptions {
/** Remote-config payload for `/api/features` (enables the flags under test). */
Expand Down Expand Up @@ -47,6 +48,7 @@ export async function mockCloudBoot(
await page.route('**/api/auth/session', (r) =>
r.fulfill(jsonRoute({ token: 'mock-workspace-token' }))
)
await mockWorkspace(page, workspace('personal', 'owner'), [])
await page.route('**/releases**', (r) => r.fulfill(jsonRoute([])))
}

Expand Down
14 changes: 7 additions & 7 deletions browser_tests/fixtures/utils/workspaceMocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Page } from '@playwright/test'
import type { BrowserContext, Page } from '@playwright/test'

import type {
Member,
Expand Down Expand Up @@ -38,10 +38,10 @@ export function member(
* this the mint fails and auth cannot resolve the active workspace.
*/
export async function mockWorkspaceTokenMint(
page: Page,
routeTarget: Page | BrowserContext,
ws: Pick<WorkspaceWithRole, 'id' | 'name' | 'type' | 'role'>
) {
await page.route('**/api/auth/token', (r) =>
await routeTarget.route('**/api/auth/token', (r) =>
r.fulfill(
jsonRoute({
token: 'mock-workspace-token',
Expand All @@ -59,16 +59,16 @@ export async function mockWorkspaceTokenMint(
* given workspace with the given roster (drives the original-owner gate).
*/
export async function mockWorkspace(
page: Page,
routeTarget: Page | BrowserContext,
ws: WorkspaceWithRole,
members: Member[]
) {
await page.route('**/api/workspaces', async (route) => {
await routeTarget.route('**/api/workspaces', async (route) => {
if (route.request().method() !== 'GET') return route.fallback()
await route.fulfill(jsonRoute({ workspaces: [ws] }))
})
await mockWorkspaceTokenMint(page, ws)
await page.route('**/api/workspace/members**', (r) =>
await mockWorkspaceTokenMint(routeTarget, ws)
await routeTarget.route('**/api/workspace/members**', (r) =>
r.fulfill(
jsonRoute({
members,
Expand Down
6 changes: 3 additions & 3 deletions browser_tests/tests/authAccountSwitch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import type { WorkspaceTokenResponse } from '@/platform/workspace/stores/workspa
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { AssetsSidebarTab } from '@e2e/fixtures/components/SidebarTab'
import {
CLOUD_REMOTE_CONFIG,
DEFAULT_TEAM_MEMBERS,
TEAM_WORKSPACE,
WORKSPACE_FEATURE_FLAG
TEAM_WORKSPACE
} from '@e2e/fixtures/data/cloudWorkspace'
import { AssetsHelper, createMockJob } from '@e2e/fixtures/helpers/AssetsHelper'
import { CloudWorkspaceMockHelper } from '@e2e/fixtures/helpers/CloudWorkspaceMockHelper'
Expand Down Expand Up @@ -141,7 +141,7 @@ test.describe('Cloud account switch', { tag: '@cloud' }, () => {
await page.unroute('**/securetoken.googleapis.com/**')

const features = {
...WORKSPACE_FEATURE_FLAG,
...CLOUD_REMOTE_CONFIG,
onboarding_survey_enabled: false,
unified_cloud_auth: false
} satisfies RemoteConfig
Expand Down
Loading
Loading