Compare commits

...

4 Commits

Author SHA1 Message Date
Connor Byrne
9bca2e3559 fix: rewrite credit helpers E2E test to match actual component behavior
- In NodeSearchContent, showDescription=true means NodePricingBadge is
  NOT rendered (only provider badges). Adjusted search test to verify
  API node indicator instead of pricing badge.
- Split VueNodes header test into separate tests for each pricing type
  (USD, range, list) for better isolation and clearer failures.
- Fixed icon class selector from non-existent `comfy--credits` to actual
  `lucide--component` used by CreditBadge.
- Added proper setup: clearGraph, enable ShowApiPricing setting.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11578#pullrequestreview-2917421451

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 12:17:37 -07:00
Connor Byrne
9e1d72a625 Merge remote-tracking branch 'origin/main' into glary/add-credit-helpers-e2e-test 2026-05-11 13:21:09 -07:00
Christian Byrne
2e0bedce12 Merge branch 'main' into glary/add-credit-helpers-e2e-test 2026-05-11 11:52:02 -07:00
Glary-Bot
61b431a79f test: add coverage for credit helper functions
- Unit tests: cover line 25 minimumFractionDigits guard and clampUsd
- E2E tests: verify pricing badge rendering via node search and VueNodes
  graph header, covering formatCredits, CREDITS_PER_USD, and formatNumber
  through USD, range, and list pricing result types
2026-05-04 22:47:36 +00:00
2 changed files with 243 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { createMockNodeDefinitions } from '@e2e/fixtures/data/nodeDefinitions'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
function buildMockApiNode(
name: string,
displayName: string,
expr: string
): ComfyNodeDef {
return {
name,
display_name: displayName,
description: 'Test API node for pricing badge checks',
category: 'testing',
input: { required: { image: ['IMAGE', {}] } },
output: ['IMAGE'],
output_is_list: [false],
output_name: ['IMAGE'],
output_node: false,
python_module: 'test_nodes',
deprecated: false,
experimental: false,
api_node: true,
price_badge: {
engine: 'jsonata',
expr,
depends_on: { widgets: [], inputs: [], input_groups: [] }
}
}
}
const MOCK_API_NODES: Record<string, ComfyNodeDef> = {
TestCreditApiNodeUsd: buildMockApiNode(
'TestCreditApiNodeUsd',
'Test Credit API Node USD',
'{"type":"usd","usd":0.05}'
),
TestCreditApiNodeRange: buildMockApiNode(
'TestCreditApiNodeRange',
'Test Credit API Node Range',
'{"type":"range_usd","min_usd":0.01,"max_usd":0.10}'
),
TestCreditApiNodeList: buildMockApiNode(
'TestCreditApiNodeList',
'Test Credit API Node List',
'{"type":"list_usd","usd":[0.02,0.05]}'
)
}
const testWithMockedObjectInfo = test.extend<{ mockApiNodes: void }>({
mockApiNodes: [
async ({ page }, use) => {
const nodeDefs = createMockNodeDefinitions(MOCK_API_NODES)
const pattern = '**/api/object_info'
await page.route(pattern, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(nodeDefs)
})
)
await use()
await page.unroute(pattern)
},
{ auto: true }
]
})
testWithMockedObjectInfo.describe(
'Credit helper pricing badges',
{ tag: '@node' },
() => {
testWithMockedObjectInfo.use({ locale: 'en-US' })
testWithMockedObjectInfo.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.LinkRelease.Action',
'search box'
)
await comfyPage.settings.setSetting(
'Comfy.LinkRelease.ActionShift',
'search box'
)
})
testWithMockedObjectInfo(
'shows API node indicator in node search results',
async ({ comfyPage }) => {
await comfyPage.canvasOps.doubleClick()
await expect(comfyPage.searchBoxV2.input).toBeVisible()
await comfyPage.searchBoxV2.input.fill('TestCreditApiNodeUsd')
const result = comfyPage.searchBoxV2.results
.filter({ hasText: 'Test Credit API Node USD' })
.first()
await expect(result).toBeVisible()
// In search results with showDescription=true, the component icon is shown
// (not the pricing badge). Verify the API node indicator is present.
const apiIndicator = result.locator('i[class*="lucide--component"]')
await expect(apiIndicator).toBeVisible()
}
)
testWithMockedObjectInfo(
'shows pricing badge in VueNodes node header',
async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.settings.setSetting(
'Comfy.NodeBadge.ShowApiPricing',
true
)
await comfyPage.nodeOps.clearGraph()
const nodeId = await comfyPage.page.evaluate(() => {
const node = window.LiteGraph!.createNode('TestCreditApiNodeUsd')
window.app!.graph.add(node!)
return node!.id
})
await comfyPage.vueNodes.waitForNodes(1)
const header = comfyPage.page.locator(
`[data-testid="node-header-${nodeId}"]`
)
await expect(header).toBeVisible()
// CreditBadge uses icon-[lucide--component] for the credits icon
const creditsBadge = header.locator('i[class*="lucide--component"]')
await expect(creditsBadge).toBeVisible()
// Verify the badge text contains expected credit amount (10.6 credits for $0.05)
const badgeContainer = header.locator(
'span:has(> i[class*="lucide--component"])'
)
await expect
.poll(async () => (await badgeContainer.textContent())?.trim() ?? '')
.toContain('10.6')
}
)
testWithMockedObjectInfo(
'shows range pricing in VueNodes node header',
async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.settings.setSetting(
'Comfy.NodeBadge.ShowApiPricing',
true
)
await comfyPage.nodeOps.clearGraph()
const nodeId = await comfyPage.page.evaluate(() => {
const node = window.LiteGraph!.createNode('TestCreditApiNodeRange')
window.app!.graph.add(node!)
return node!.id
})
await comfyPage.vueNodes.waitForNodes(1)
const header = comfyPage.page.locator(
`[data-testid="node-header-${nodeId}"]`
)
await expect(header).toBeVisible()
// Verify range format (2.1-21.1 credits for $0.01-$0.10)
const badgeContainer = header.locator(
'span:has(> i[class*="lucide--component"])'
)
await expect
.poll(async () => (await badgeContainer.textContent())?.trim() ?? '')
.toContain('2.1-21.1')
}
)
testWithMockedObjectInfo(
'shows list pricing in VueNodes node header',
async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.settings.setSetting(
'Comfy.NodeBadge.ShowApiPricing',
true
)
await comfyPage.nodeOps.clearGraph()
const nodeId = await comfyPage.page.evaluate(() => {
const node = window.LiteGraph!.createNode('TestCreditApiNodeList')
window.app!.graph.add(node!)
return node!.id
})
await comfyPage.vueNodes.waitForNodes(1)
const header = comfyPage.page.locator(
`[data-testid="node-header-${nodeId}"]`
)
await expect(header).toBeVisible()
// Verify list format (4.2/10.6 credits for [$0.02, $0.05])
const badgeContainer = header.locator(
'span:has(> i[class*="lucide--component"])'
)
await expect
.poll(async () => (await badgeContainer.textContent())?.trim() ?? '')
.toContain('4.2/10.6')
}
)
}
)

View File

@@ -4,6 +4,7 @@ import {
CREDITS_PER_USD,
COMFY_CREDIT_RATE_CENTS,
centsToCredits,
clampUsd,
creditsToCents,
creditsToUsd,
formatCredits,
@@ -43,4 +44,29 @@ describe('comfyCredits helpers', () => {
expect(formatCreditsFromUsd({ usd: 1, locale })).toBe('211.00')
expect(formatUsd({ value: 4.2, locale })).toBe('4.20')
})
test('clamps minimumFractionDigits when maximumFractionDigits is lower than default', () => {
expect(
formatCredits({
value: 1.5,
locale: 'en-US',
numberOptions: { maximumFractionDigits: 0 }
})
).toBe('2')
expect(
formatUsd({
value: 3.456,
locale: 'en-US',
numberOptions: { maximumFractionDigits: 1 }
})
).toBe('3.5')
})
test('clampUsd clamps values to the allowed purchase range', () => {
expect(clampUsd(50)).toBe(50)
expect(clampUsd(0.5)).toBe(1)
expect(clampUsd(2000)).toBe(1000)
expect(clampUsd(NaN)).toBe(0)
})
})