Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9a401cc
test: add critical unit coverage gate
huang47 Jun 28, 2026
9b9fe24
test: remove unrelated website e2e guard
huang47 Jun 29, 2026
1ec51d9
test: broaden critical coverage gate scope
huang47 Jun 29, 2026
3f8cf13
test: drop unused coverage summary reporter
huang47 Jun 29, 2026
25e73f3
test: expand critical coverage subdomains
huang47 Jun 29, 2026
a7f7471
test: cover utility and base branch gaps
huang47 Jun 28, 2026
1befd2c
test: cover graph selection and menu composables
huang47 Jun 28, 2026
fce7342
test: cover metadata parser and manager pack composables
huang47 Jun 28, 2026
95c43a7
test: cover critical store branches
huang47 Jun 28, 2026
34ec250
test: cover queue models and execution-store slices
huang47 Jun 28, 2026
ba08730
test: cover subgraph and workflow-management stores
huang47 Jun 28, 2026
4dcc988
test: cover asset, model and registry stores
huang47 Jun 28, 2026
fe17d92
test: cover session and UI stores
huang47 Jun 28, 2026
89ffa51
test: cover system and workspace stores
huang47 Jun 28, 2026
2a0597f
test: cover composables, renderer and util helpers
huang47 Jun 28, 2026
b528e58
test: add chromium @critical browser lane
huang47 Jun 28, 2026
2071a14
test: add cloud-critical browser lane
huang47 Jun 28, 2026
2a5917e
ci: skip website report deploy for fork PRs
huang47 Jun 28, 2026
ea91c63
refactor: extract filename via lastIndexOf instead of split/pop
huang47 Jun 28, 2026
bfaceda
refactor: remove dead branches and tidy store cleanups
huang47 Jun 28, 2026
d18a234
refactor: tidy executionStore job-state handling
huang47 Jun 28, 2026
ca84401
refactor: thread validated state through subgraph save
huang47 Jun 29, 2026
99d97f9
refactor: handle asset settle failures per-promise
huang47 Jun 28, 2026
7452aa3
refactor: simplify subgraph viewport save branching
huang47 Jun 28, 2026
808c19d
Merge commit 'a7f74718995ea5cb712d37e193f41b0d83b26981' into HEAD
huang47 Jun 30, 2026
98697b0
Merge commit '1befd2cc1f42ae1d935deec699d82396a660d50d' into HEAD
huang47 Jun 30, 2026
41368f2
Merge commit 'fce7342279d9b0dd7353431cec1999636683c92c' into HEAD
huang47 Jun 30, 2026
72d0e0f
Merge commit '95c43a708a8105f99ce03ebd306fecf0417852fc' into HEAD
huang47 Jun 30, 2026
6f3b61c
Merge commit '34ec25007c536a6c86be49d77ad4fe1e8bffc791' into HEAD
huang47 Jun 30, 2026
0ed758f
Merge commit 'ba08730877f802e750947dcfac375c6e4fa64e47' into HEAD
huang47 Jun 30, 2026
557b20c
Merge commit '4dcc9885301e0acd1bfb84fda99c95b6dcb3e3d5' into HEAD
huang47 Jun 30, 2026
5cabf69
Merge commit 'fe17d92016314bc5ed37d8dd7eef02f96d2ef709' into HEAD
huang47 Jun 30, 2026
da4a749
Merge commit '89ffa51130f23a63b3c3b229af4d68c0d1d514ad' into HEAD
huang47 Jun 30, 2026
50ef85f
Merge commit '2a0597fb3b1450eaa41bd14bb0a874e512ffa976' into HEAD
huang47 Jun 30, 2026
772d38a
Merge commit 'b528e583238dd5e654580d5474cbd70f90a7c7cb' into HEAD
huang47 Jun 30, 2026
c7190ea
Merge commit '2071a145cc4a2b64bd733f6bba543e8fd1ec9cc7' into HEAD
huang47 Jun 30, 2026
833b0be
Merge remote-tracking branch 'shihchi/shihchi/ci-website-fork-skip' i…
huang47 Jun 30, 2026
ea6ec4b
Merge remote-tracking branch 'shihchi/shihchi/refactor-string-extract…
huang47 Jun 30, 2026
11a9a0f
Merge remote-tracking branch 'shihchi/shihchi/refactor-dead-branch-cl…
huang47 Jun 30, 2026
c5be287
Merge remote-tracking branch 'shihchi/shihchi/refactor-execution-stor…
huang47 Jun 30, 2026
5aa84c5
Merge remote-tracking branch 'shihchi/shihchi/refactor-subgraph-store…
huang47 Jun 30, 2026
df60c72
Merge remote-tracking branch 'shihchi/shihchi/refactor-assets-store-s…
huang47 Jun 30, 2026
1eacf7a
Merge remote-tracking branch 'shihchi/shihchi/refactor-subgraph-nav-c…
huang47 Jun 30, 2026
2b90fec
test: strengthen coverage assertions
huang47 Jun 30, 2026
fa4e74f
fix: guard subgraph blueprint root node
huang47 Jun 30, 2026
0a2db26
Merge remote-tracking branch 'shihchi/shihchi/coverage-composables-re…
huang47 Jun 30, 2026
dcd4b84
Merge remote-tracking branch 'shihchi/shihchi/refactor-subgraph-store…
huang47 Jun 30, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/ci-tests-unit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ jobs:
flags: unit
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false

- name: Enforce critical coverage gate
run: pnpm test:coverage:critical
10 changes: 9 additions & 1 deletion .github/workflows/ci-website-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ jobs:

- name: Deploy report to Cloudflare
id: deploy
if: always() && !cancelled()
if: >-
${{
always() &&
!cancelled() &&
(
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.fork == false
)
}}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand Down
64 changes: 36 additions & 28 deletions browser_tests/tests/cloud.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,44 @@ const SHARE_AUTH_STORAGE_KEY = 'Comfy.PreservedQuery.share_auth'
* routes and elements.
*/
test.describe('Cloud distribution UI', { tag: '@cloud' }, () => {
test('cloud build redirects unauthenticated users to login', async ({
page
}) => {
await page.goto(APP_URL)
// Cloud build has an auth guard that redirects to /cloud/login.
// This route only exists in the cloud distribution — it's tree-shaken
// in the OSS build. Its presence confirms the cloud build is active.
await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
})
test(
'cloud build redirects unauthenticated users to login',
{ tag: '@critical' },
async ({ page }) => {
await page.goto(APP_URL)
// Cloud build has an auth guard that redirects to /cloud/login.
// This route only exists in the cloud distribution — it's tree-shaken
// in the OSS build. Its presence confirms the cloud build is active.
await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
}
)

test('preserves share auth attribution before redirecting logged-out users', async ({
page
}) => {
await page.goto(new URL('/?share=abc', APP_URL).toString())
test(
'preserves share auth attribution before redirecting logged-out users',
{ tag: '@critical' },
async ({ page }) => {
await page.goto(new URL('/?share=abc', APP_URL).toString())

await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
await expect
.poll(() =>
page.evaluate(
(key) => sessionStorage.getItem(key),
SHARE_AUTH_STORAGE_KEY
await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
await expect
.poll(() =>
page.evaluate(
(key) => sessionStorage.getItem(key),
SHARE_AUTH_STORAGE_KEY
)
)
)
.toBe(JSON.stringify({ share: 'abc' }))
})
.toBe(JSON.stringify({ share: 'abc' }))
}
)

test('cloud login page renders sign-in options', async ({ page }) => {
await page.goto(APP_URL)
await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
// Verify cloud-specific login UI is rendered
await expect(page.getByRole('button', { name: /google/i })).toBeVisible()
})
test(
'cloud login page renders sign-in options',
{ tag: '@critical' },
async ({ page }) => {
await page.goto(APP_URL)
await expect(page).toHaveURL(/\/cloud\/login/, { timeout: 10_000 })
// Verify cloud-specific login UI is rendered
await expect(page.getByRole('button', { name: /google/i })).toBeVisible()
}
)
})
60 changes: 32 additions & 28 deletions browser_tests/tests/execution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,34 +97,38 @@ test.describe(
'Execute to selected output nodes',
{ tag: ['@smoke', '@workflow'] },
() => {
test('Execute to selected output nodes', async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('execution/partial_execution')
const input = await comfyPage.nodeOps.getNodeRefById(3)
const output1 = await comfyPage.nodeOps.getNodeRefById(1)
const output2 = await comfyPage.nodeOps.getNodeRefById(4)
await expect
.poll(async () => (await input.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output1.getWidget(0)).getValue())
.toBe('')
await expect
.poll(async () => (await output2.getWidget(0)).getValue())
.toBe('')

await output1.click('title')

await comfyPage.command.executeCommand('Comfy.QueueSelectedOutputNodes')
await expect
.poll(async () => (await input.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output1.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output2.getWidget(0)).getValue())
.toBe('')
})
test(
'Execute to selected output nodes',
{ tag: '@critical' },
async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('execution/partial_execution')
const input = await comfyPage.nodeOps.getNodeRefById(3)
const output1 = await comfyPage.nodeOps.getNodeRefById(1)
const output2 = await comfyPage.nodeOps.getNodeRefById(4)
await expect
.poll(async () => (await input.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output1.getWidget(0)).getValue())
.toBe('')
await expect
.poll(async () => (await output2.getWidget(0)).getValue())
.toBe('')

await output1.click('title')

await comfyPage.command.executeCommand('Comfy.QueueSelectedOutputNodes')
await expect
.poll(async () => (await input.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output1.getWidget(0)).getValue())
.toBe('foo')
await expect
.poll(async () => (await output2.getWidget(0)).getValue())
.toBe('')
}
)
}
)

Expand Down
52 changes: 28 additions & 24 deletions browser_tests/tests/extensionAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,37 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
type TestSettingId = keyof Settings

test.describe('Topbar commands', () => {
test('Should allow registering topbar commands', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window.app!.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'foo',
label: 'foo-command',
function: () => {
window.foo = true
test(
'Should allow registering topbar commands',
{ tag: '@critical' },
async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window.app!.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'foo',
label: 'foo-command',
function: () => {
window.foo = true
}
}
}
],
menuCommands: [
{
path: ['ext'],
commands: ['foo']
}
]
],
menuCommands: [
{
path: ['ext'],
commands: ['foo']
}
]
})
})
})

await comfyPage.menu.topbar.triggerTopbarCommand(['ext', 'foo-command'])
await expect
.poll(() => comfyPage.page.evaluate(() => window.foo))
.toBe(true)
})
await comfyPage.menu.topbar.triggerTopbarCommand(['ext', 'foo-command'])
await expect
.poll(() => comfyPage.page.evaluate(() => window.foo))
.toBe(true)
}
)

test('Should not allow register command defined in other extension', async ({
comfyPage
Expand Down
14 changes: 9 additions & 5 deletions browser_tests/tests/graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@
await expect
.poll(() =>
comfyPage.page.evaluate(() => {
return window.app!.graph!.links.get(1)?.target_slot

Check failure on line 19 in browser_tests/tests/graph.spec.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Argument of type 'number' is not assignable to parameter of type 'LinkId'.
})
)
.toBe(1)
})

test('Validate workflow links', async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.Validation.Workflows', true)
await comfyPage.workflow.loadWorkflow('links/bad_link')
await expect(comfyPage.toast.visibleToasts).toHaveCount(2)
})
test(
'Validate workflow links',
{ tag: '@critical' },
async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.Validation.Workflows', true)
await comfyPage.workflow.loadWorkflow('links/bad_link')
await expect(comfyPage.toast.visibleToasts).toHaveCount(2)
}
)

// Regression: duplicate links with shifted target_slot (widget-to-input
// conversion) caused the wrong link to survive during deduplication.
Expand Down
28 changes: 16 additions & 12 deletions browser_tests/tests/nodeSearchBoxV2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ test.describe('Node search box V2', { tag: '@node' }, () => {
await comfyPage.searchBoxV2.setup()
})

test('Can open search and add node', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
test(
'Can open search and add node',
{ tag: '@critical' },
async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()

await searchBoxV2.open()
await searchBoxV2.input.fill('KSampler')
await expect(searchBoxV2.results.first()).toBeVisible()
await searchBoxV2.open()
await searchBoxV2.input.fill('KSampler')
await expect(searchBoxV2.results.first()).toBeVisible()

await comfyPage.page.keyboard.press('Enter')
await expect(searchBoxV2.input).toBeHidden()
await expect
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
.toBe(initialCount + 1)
})
await comfyPage.page.keyboard.press('Enter')
await expect(searchBoxV2.input).toBeHidden()
await expect
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
.toBe(initialCount + 1)
}
)

test('Can add first default result with Enter', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
Expand Down
26 changes: 14 additions & 12 deletions browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,21 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
await cleanupFakeModel(comfyPage)
})

test('Should show missing models group in errors tab', async ({
comfyPage
}) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
test(
'Should show missing models group in errors tab',
{ tag: '@critical' },
async ({ comfyPage }) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')

const missingModelsGroup = comfyPage.page.getByTestId(
TestIds.dialogs.missingModelsGroup
)
await expect(missingModelsGroup).toBeVisible()
await expect(
missingModelsGroup.getByTestId(TestIds.dialogs.errorGroupDisplayMessage)
).toHaveText(/\S/)
})
const missingModelsGroup = comfyPage.page.getByTestId(
TestIds.dialogs.missingModelsGroup
)
await expect(missingModelsGroup).toBeVisible()
await expect(
missingModelsGroup.getByTestId(TestIds.dialogs.errorGroupDisplayMessage)
).toHaveText(/\S/)
}
)

test('Should display model name and metadata', async ({ comfyPage }) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
Expand Down
36 changes: 19 additions & 17 deletions browser_tests/tests/propertiesPanel/errorsTabMissingNodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ test.describe('Errors tab - Missing nodes', { tag: ['@ui', '@canvas'] }, () => {
)
})

test('Should show missing node pack card with guidance', async ({
comfyPage
}) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')

const missingNodeGroup = comfyPage.page.getByTestId(
TestIds.dialogs.missingNodePacksGroup
)

await expect(
comfyPage.page.getByTestId(TestIds.dialogs.missingNodeCard)
).toBeVisible()
await expect(missingNodeGroup).toBeVisible()
await expect(
missingNodeGroup.getByTestId(TestIds.dialogs.errorGroupDisplayMessage)
).toHaveText(/\S/)
})
test(
'Should show missing node pack card with guidance',
{ tag: '@critical' },
async ({ comfyPage }) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')

const missingNodeGroup = comfyPage.page.getByTestId(
TestIds.dialogs.missingNodePacksGroup
)

await expect(
comfyPage.page.getByTestId(TestIds.dialogs.missingNodeCard)
).toBeVisible()
await expect(missingNodeGroup).toBeVisible()
await expect(
missingNodeGroup.getByTestId(TestIds.dialogs.errorGroupDisplayMessage)
).toHaveText(/\S/)
}
)

test('Should show unknown pack node rows by default', async ({
comfyPage
Expand Down
20 changes: 13 additions & 7 deletions browser_tests/tests/queue/queueOverlay.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@ test.describe('Queue overlay', () => {
await comfyPage.setup()
})

test('Toggle button opens expanded queue overlay', async ({ comfyPage }) => {
const toggle = comfyPage.page.getByTestId(TestIds.queue.overlayToggle)
await toggle.click()

// Expanded overlay should show job items
await expect(comfyPage.page.locator('[data-job-id]').first()).toBeVisible()
})
test(
'Toggle button opens expanded queue overlay',
{ tag: '@critical' },
async ({ comfyPage }) => {
const toggle = comfyPage.page.getByTestId(TestIds.queue.overlayToggle)
await toggle.click()

// Expanded overlay should show job items
await expect(
comfyPage.page.locator('[data-job-id]').first()
).toBeVisible()
}
)

test('Overlay shows filter tabs (All, Completed)', async ({ comfyPage }) => {
const toggle = comfyPage.page.getByTestId(TestIds.queue.overlayToggle)
Expand Down
2 changes: 1 addition & 1 deletion browser_tests/tests/subgraph/subgraphSerialization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async function expectPromotedWidgetsToResolveToInteriorNodes(
test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
test(
'Legacy primitive proxy widgets migrate to host inputs without proxyWidgets round-trip',
{ tag: ['@vue-nodes'] },
{ tag: ['@vue-nodes', '@critical'] },
async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow(
'subgraphs/subgraph-with-link-and-proxied-primitive'
Expand Down
Loading
Loading