feat/fix: App mode QA updates (#9439)

## Summary

Various fixes from app mode QA

## Changes

- **What**: 
- fix: prevent inserting nodes from workflow/apps sidebar tabs
- fix: hide json extension in workflow tab
- fix: hide apps nav button in apps tab when already in apps mode
- fix: center text on arrange page
- fix: prevent IoItems from "jumping" due to stale transform after drag
and drop op
- fix: refactor side panels and add custom stable pixel based sizing
- fix: make outputs/inputs lists in app builder scrollable
- fix: fix rerun not working correctly

- feat: add text to interrupt button
- feat: add enter app mode button to builder toolbar
- feat: add tooltip to download button on linear view
- feat: show last output of workflow in arrange tab if available
- feat: show download count in download all button, hide if only 1 asset
to download

## Review Focus

- Rerun - I am not sure why it was triggering widget actions, removing
it seemed like the correct fix
- useStablePrimeVueSplitter - this is a workaround for the fact it uses
percent sizing, I also tried switching to reka-ui splitters, but they
also only support % sizing in our version [pixel based looks to have
been added in a newer version, will log an issue to upgrade & replace
splitters with this]


## Screenshots (if applicable)

<img width="1314" height="1129" alt="image"
src="https://github.com/user-attachments/assets/c430f9d6-7c29-4853-803e-5b6fe7086fca"
/>
<img width="511" height="283" alt="image"
src="https://github.com/user-attachments/assets/b7e594d4-70a1-41e3-8ba1-78512f2a5c8b"
/>
<img width="254" height="232" alt="image"
src="https://github.com/user-attachments/assets/1d146399-39ea-4b0e-928c-340b74957535"
/>
<img width="487" height="198" alt="image"
src="https://github.com/user-attachments/assets/e2ba7f5d-8ff5-47f4-9526-61ebb99514b8"
/>
<img width="378" height="647" alt="image"
src="https://github.com/user-attachments/assets/a47a3054-9320-4327-bdc0-b0a16e19f83d"
/>
<img width="1016" height="476" alt="image"
src="https://github.com/user-attachments/assets/479ae50e-d380-4d56-a5c9-5df142b14ed0"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9439-feat-fix-App-mode-QA-updates-31a6d73d365081b38337d63207b88817)
by [Unito](https://www.unito.io)
This commit is contained in:
pythongosssss
2026-03-06 20:02:19 +00:00
committed by GitHub
parent bae1081a08
commit 4ff14b5eb9
16 changed files with 626 additions and 322 deletions

View File

@@ -819,16 +819,13 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
const activeWorkflowName =
await comfyPage.menu.workflowsTab.getActiveWorkflowName()
const workflowPathA = `${workflowA}.json`
const workflowPathB = `${workflowB}.json`
expect(openWorkflows).toEqual(
expect.arrayContaining([workflowPathA, workflowPathB])
expect.arrayContaining([workflowA, workflowB])
)
expect(openWorkflows.indexOf(workflowPathA)).toBeLessThan(
openWorkflows.indexOf(workflowPathB)
expect(openWorkflows.indexOf(workflowA)).toBeLessThan(
openWorkflows.indexOf(workflowB)
)
expect(activeWorkflowName).toEqual(workflowPathB)
expect(activeWorkflowName).toEqual(workflowB)
})
})

View File

@@ -13,9 +13,9 @@ test.describe('Reroute Node', { tag: ['@screenshot', '@node'] }, () => {
})
test('loads from inserted workflow', async ({ comfyPage }) => {
const workflowName = 'single_connected_reroute_node.json'
const workflowName = 'single_connected_reroute_node'
await comfyPage.workflow.setupWorkflowsDirectory({
[workflowName]: 'links/single_connected_reroute_node.json'
[`${workflowName}.json`]: `links/${workflowName}.json`
})
await comfyPage.setup()
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])

View File

@@ -21,14 +21,12 @@ test.describe('Workflows sidebar', () => {
test('Can create new blank workflow', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
expect(await tab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
expect(await tab.getOpenedWorkflowNames()).toEqual(['*Unsaved Workflow'])
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json',
'*Unsaved Workflow (2).json'
'*Unsaved Workflow',
'*Unsaved Workflow (2)'
])
})
@@ -41,37 +39,37 @@ test.describe('Workflows sidebar', () => {
const tab = comfyPage.menu.workflowsTab
await tab.open()
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
expect.arrayContaining(['workflow1.json', 'workflow2.json'])
expect.arrayContaining(['workflow1', 'workflow2'])
)
})
test('Can duplicate workflow', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
await comfyPage.menu.topbar.saveWorkflow('workflow1')
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
expect.arrayContaining(['workflow1.json'])
expect.arrayContaining(['workflow1'])
)
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json'
'workflow1',
'*workflow1 (Copy)'
])
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json',
'*workflow1 (Copy) (2).json'
'workflow1',
'*workflow1 (Copy)',
'*workflow1 (Copy) (2)'
])
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json',
'*workflow1 (Copy) (2).json',
'*workflow1 (Copy) (3).json'
'workflow1',
'*workflow1 (Copy)',
'*workflow1 (Copy) (2)',
'*workflow1 (Copy) (3)'
])
})
@@ -85,12 +83,12 @@ test.describe('Workflows sidebar', () => {
await comfyPage.command.executeCommand('Comfy.LoadDefaultWorkflow')
const originalNodeCount = await comfyPage.nodeOps.getNodeCount()
await tab.insertWorkflow(tab.getPersistedItem('workflow1.json'))
await tab.insertWorkflow(tab.getPersistedItem('workflow1'))
await expect
.poll(() => comfyPage.nodeOps.getNodeCount())
.toEqual(originalNodeCount + 1)
await tab.getPersistedItem('workflow1.json').click()
await tab.getPersistedItem('workflow1').click()
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toEqual(1)
})
@@ -113,22 +111,22 @@ test.describe('Workflows sidebar', () => {
const openedWorkflow = tab.getOpenedItem('foo/bar')
await tab.renameWorkflow(openedWorkflow, 'foo/baz')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json',
'foo/baz.json'
'*Unsaved Workflow',
'foo/baz'
])
})
test('Can save workflow as', async ({ comfyPage }) => {
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
await comfyPage.menu.topbar.saveWorkflowAs('workflow3.json')
await comfyPage.menu.topbar.saveWorkflowAs('workflow3')
await expect
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
.toEqual(['*Unsaved Workflow.json', 'workflow3.json'])
.toEqual(['*Unsaved Workflow', 'workflow3'])
await comfyPage.menu.topbar.saveWorkflowAs('workflow4.json')
await comfyPage.menu.topbar.saveWorkflowAs('workflow4')
await expect
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
.toEqual(['*Unsaved Workflow.json', 'workflow3.json', 'workflow4.json'])
.toEqual(['*Unsaved Workflow', 'workflow3', 'workflow4'])
})
test('Exported workflow does not contain localized slot names', async ({
@@ -184,15 +182,15 @@ test.describe('Workflows sidebar', () => {
})
test('Can save workflow as with same name', async ({ comfyPage }) => {
await comfyPage.menu.topbar.saveWorkflow('workflow5.json')
await comfyPage.menu.topbar.saveWorkflow('workflow5')
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
'workflow5.json'
'workflow5'
])
await comfyPage.menu.topbar.saveWorkflowAs('workflow5.json')
await comfyPage.menu.topbar.saveWorkflowAs('workflow5')
await comfyPage.confirmDialog.click('overwrite')
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
'workflow5.json'
'workflow5'
])
})
@@ -212,25 +210,25 @@ test.describe('Workflows sidebar', () => {
test('Can overwrite other workflows with save as', async ({ comfyPage }) => {
const topbar = comfyPage.menu.topbar
await topbar.saveWorkflow('workflow1.json')
await topbar.saveWorkflowAs('workflow2.json')
await topbar.saveWorkflow('workflow1')
await topbar.saveWorkflowAs('workflow2')
await comfyPage.nextFrame()
await expect
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
.toEqual(['workflow1.json', 'workflow2.json'])
.toEqual(['workflow1', 'workflow2'])
await expect
.poll(() => comfyPage.menu.workflowsTab.getActiveWorkflowName())
.toEqual('workflow2.json')
.toEqual('workflow2')
await topbar.saveWorkflowAs('workflow1.json')
await topbar.saveWorkflowAs('workflow1')
await comfyPage.confirmDialog.click('overwrite')
// The old workflow1.json should be deleted and the new one should be saved.
// The old workflow1 should be deleted and the new one should be saved.
await expect
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
.toEqual(['workflow2.json', 'workflow1.json'])
.toEqual(['workflow2', 'workflow1'])
await expect
.poll(() => comfyPage.menu.workflowsTab.getActiveWorkflowName())
.toEqual('workflow1.json')
.toEqual('workflow1')
})
test('Does not report warning when switching between opened workflows', async ({
@@ -266,17 +264,15 @@ test.describe('Workflows sidebar', () => {
)
await closeButton.click()
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
'*Unsaved Workflow'
])
})
test('Can close saved workflow with command', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
await comfyPage.menu.topbar.saveWorkflow('workflow1')
await comfyPage.command.executeCommand('Workspace.CloseWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
expect(await tab.getOpenedWorkflowNames()).toEqual(['*Unsaved Workflow'])
})
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
@@ -284,7 +280,7 @@ test.describe('Workflows sidebar', () => {
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
const filename = 'workflow18'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
@@ -295,14 +291,14 @@ test.describe('Workflows sidebar', () => {
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
'*Unsaved Workflow'
])
})
test('Can delete workflows', async ({ comfyPage }) => {
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
const filename = 'workflow18'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
@@ -314,7 +310,7 @@ test.describe('Workflows sidebar', () => {
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
'*Unsaved Workflow'
])
})
@@ -326,13 +322,11 @@ test.describe('Workflows sidebar', () => {
const { workflowsTab } = comfyPage.menu
await workflowsTab.open()
await workflowsTab
.getPersistedItem('workflow1.json')
.click({ button: 'right' })
await workflowsTab.getPersistedItem('workflow1').click({ button: 'right' })
await comfyPage.contextMenu.clickMenuItem('Duplicate')
await expect
.poll(() => workflowsTab.getOpenedWorkflowNames())
.toEqual(['*Unsaved Workflow.json', '*workflow1 (Copy).json'])
.toEqual(['*Unsaved Workflow', '*workflow1 (Copy)'])
})
test('Can drop workflow from workflows sidebar', async ({ comfyPage }) => {
@@ -344,7 +338,7 @@ test.describe('Workflows sidebar', () => {
// Wait for workflow to appear in Browse section after sync
const workflowItem =
comfyPage.menu.workflowsTab.getPersistedItem('workflow1.json')
comfyPage.menu.workflowsTab.getPersistedItem('workflow1')
await expect(workflowItem).toBeVisible({ timeout: 3000 })
const nodeCount = await comfyPage.nodeOps.getGraphNodesCount()
@@ -361,7 +355,7 @@ test.describe('Workflows sidebar', () => {
}
await comfyPage.page.dragAndDrop(
'.comfyui-workflows-browse .node-label:has-text("workflow1.json")',
'.comfyui-workflows-browse .node-label:has-text("workflow1")',
'#graph-canvas',
{ targetPosition }
)