Files
ComfyUI_frontend/browser_tests/tests/browserTabTitleMultiNode.spec.ts
2025-07-17 18:27:09 -07:00

317 lines
10 KiB
TypeScript

import { expect, mergeTests } from '@playwright/test'
import { comfyPageFixture } from '../fixtures/ComfyPage'
import { webSocketFixture } from '../fixtures/ws'
import {
BrowserTitleMonitor,
ExecutionTestHelper
} from '../helpers/ExecutionTestHelper'
const test = mergeTests(comfyPageFixture, webSocketFixture)
test.describe('Browser Tab Title - Multi-node Execution', () => {
test.describe.configure({ mode: 'serial' })
let executionHelper: ExecutionTestHelper
let titleMonitor: BrowserTitleMonitor
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
executionHelper = new ExecutionTestHelper(comfyPage.page)
titleMonitor = new BrowserTitleMonitor(comfyPage.page)
})
test.afterEach(async () => {
// Clean up event listeners to avoid conflicts
if (executionHelper) {
await executionHelper.cleanup()
}
})
test('Shows multiple nodes running in tab title', async ({
comfyPage,
ws
}) => {
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
// Wait for any existing execution to complete
await titleMonitor.waitForIdleTitle()
// Get initial title
const initialTitle = await comfyPage.page.title()
// Title might show execution state if other tests are running
// Just ensure we have a baseline to compare against
// Wait for the UI to be ready
await comfyPage.nextFrame()
// Check if workflow is valid and nodes are available
const workflowStatus = await comfyPage.page.evaluate(() => {
const graph = window['app'].graph
const missingNodeTypes: string[] = []
const nodeCount = graph.nodes.length
// Check for missing node types
graph.nodes.forEach((node: any) => {
if (node.type && !LiteGraph.registered_node_types[node.type]) {
missingNodeTypes.push(node.type)
}
})
return {
nodeCount,
missingNodeTypes,
hasErrors: missingNodeTypes.length > 0
}
})
if (workflowStatus.hasErrors) {
console.log('Missing node types:', workflowStatus.missingNodeTypes)
// Skip test if nodes are missing
test.skip()
return
}
// Set up tracking for progress events and errors
await executionHelper.setupEventTracking()
// Queue the workflow for real execution using the command
await comfyPage.executeCommand('Comfy.QueuePrompt')
// Wait a moment to see if there's an error
await comfyPage.page.waitForTimeout(1000)
// Check for execution errors
if (await executionHelper.hasExecutionError()) {
const error = await executionHelper.getExecutionError()
console.log('Execution error:', error)
}
// Wait for multiple nodes to be running (TestSleep nodes 2, 3 and TestAsyncProgressNode 4)
await executionHelper.waitForRunningNodes(2)
// Check title while we know multiple nodes are running
const testId = executionHelper.getTestId()
const titleDuringExecution = await comfyPage.page.evaluate((testId) => {
const states = window[`__progressStates_${testId}`]
if (!states || states.length === 0) return null
const latestState = states[states.length - 1]
if (!latestState.nodes) return null
const runningNodes = Object.values(latestState.nodes).filter(
(node: any) => node.state === 'running'
).length
return {
title: document.title,
runningCount: runningNodes
}
}, testId)
// Verify we captured the state with multiple nodes running
expect(titleDuringExecution).not.toBeNull()
expect(titleDuringExecution.runningCount).toBeGreaterThanOrEqual(2)
// The title should show multiple nodes running when we have 2+ nodes executing
if (titleDuringExecution.runningCount >= 2) {
expect(titleDuringExecution.title).toMatch(/\[\d+ nodes running\]/)
}
// Wait for some nodes to finish, leaving only one running
await executionHelper.waitForRunningNodes(1, 15000)
// Wait for title to show single node progress
await comfyPage.page.waitForFunction(
() => {
const title = document.title
return title.match(/\[\d+%\]/) && !title.match(/\[\d+ nodes running\]/)
},
{ timeout: 5000 }
)
// Check that title shows single node with progress
const titleWithSingleNode = await comfyPage.page.title()
expect(titleWithSingleNode).toMatch(/\[\d+%\]/)
expect(titleWithSingleNode).not.toMatch(/\[\d+ nodes running\]/)
})
test('Shows progress updates in title during execution', async ({
comfyPage,
ws
}) => {
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
// Wait for the UI to be ready
await comfyPage.nextFrame()
// Set up tracking for progress events and title changes
await executionHelper.setupEventTracking()
await titleMonitor.setupTitleMonitoring()
// Queue the workflow for real execution using the command
await comfyPage.executeCommand('Comfy.QueuePrompt')
// Wait for TestAsyncProgressNode (node 4) to start showing progress
// This node reports progress from 0 to 10 with steps of 1
const testId2 = executionHelper.getTestId()
await comfyPage.page.waitForFunction(
(testId) => {
const states = window[`__progressStates_${testId}`]
if (!states || states.length === 0) return false
const latestState = states[states.length - 1]
if (!latestState.nodes || !latestState.nodes['4']) return false
const node4 = latestState.nodes['4']
if (node4.state === 'running' && node4.value > 0) {
window['__lastProgress'] = Math.round((node4.value / node4.max) * 100)
return true
}
return false
},
testId2,
{ timeout: 10000 }
)
// Wait for title to show progress percentage
await comfyPage.page.waitForFunction(
() => {
const title = document.title
console.log('Title check 1:', title)
return title.match(/\[\d+%\]/)
},
{ timeout: 5000 }
)
// Check that title shows a progress percentage
const titleWithProgress = await comfyPage.page.title()
expect(titleWithProgress).toMatch(/\[\d+%\]/)
// Wait for progress to update to a different value
const firstProgress = await comfyPage.page.evaluate(
() => window['__lastProgress']
)
const testId3 = executionHelper.getTestId()
await comfyPage.page.waitForFunction(
({ initialProgress, testId }) => {
const states = window[`__progressStates_${testId}`]
if (!states || states.length === 0) return false
const latestState = states[states.length - 1]
if (!latestState.nodes || !latestState.nodes['4']) return false
const node4 = latestState.nodes['4']
if (node4.state === 'running') {
const currentProgress = Math.round((node4.value / node4.max) * 100)
window['__lastProgress'] = currentProgress
return currentProgress > initialProgress
}
return false
},
{ initialProgress: firstProgress, testId: testId3 },
{ timeout: 10000 }
)
// Store the first progress for comparison
await comfyPage.page.evaluate((progress) => {
window['__firstProgress'] = progress
}, firstProgress)
// Check the title history to verify we captured progress updates
const finalCheck = await comfyPage.page.evaluate(() => {
const titleLog = window['__titleUpdateLog'] || []
const firstProgress = window['__firstProgress'] || 0
// Find titles with progress information
const titlesWithProgress = titleLog.filter((entry) => entry.hasProgress)
// Check if we saw different progress values or multi-node running state
const progressValues = new Set()
const hadMultiNodeRunning = titleLog.some((entry) =>
entry.title.includes('nodes running')
)
titleLog.forEach((entry) => {
const match = entry.title.match(/\[(\d+)%\]/)
if (match) {
progressValues.add(parseInt(match[1]))
}
})
return {
sawProgressUpdates: titlesWithProgress.length > 0,
uniqueProgressValues: Array.from(progressValues),
hadMultiNodeRunning,
firstProgress,
lastProgress: window['__lastProgress'],
totalTitleUpdates: titleLog.length,
sampleTitles: titleLog.slice(0, 5)
}
})
console.log('Title update check:', JSON.stringify(finalCheck, null, 2))
// Verify that we captured title updates showing execution progress
expect(finalCheck.sawProgressUpdates).toBe(true)
expect(finalCheck.totalTitleUpdates).toBeGreaterThan(0)
// We should have seen either:
// 1. Multiple unique progress values, OR
// 2. Multi-node running state, OR
// 3. Progress different from initial
const sawProgressChange =
finalCheck.uniqueProgressValues.length > 1 ||
finalCheck.hadMultiNodeRunning ||
finalCheck.lastProgress !== firstProgress
expect(sawProgressChange).toBe(true)
// Clean up interval
await titleMonitor.stopTitleMonitoring()
})
test('Clears execution status from title when all nodes finish', async ({
comfyPage,
ws
}) => {
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
// Wait for any existing execution to complete
await titleMonitor.waitForIdleTitle()
// Wait for the UI to be ready
await comfyPage.nextFrame()
// Set up tracking for events
await executionHelper.setupEventTracking()
// Queue the workflow for real execution using the command
await comfyPage.executeCommand('Comfy.QueuePrompt')
// Wait for execution to show progress in title
await titleMonitor.waitForExecutionTitle()
// Verify execution shows in title
const executingTitle = await comfyPage.page.title()
expect(executingTitle).toMatch(/\[[\d%\s\w]+\]/)
// Wait for execution to complete (all nodes finished)
await executionHelper.waitForExecutionFinish()
// Give a moment for title to update after execution completes
await comfyPage.page.waitForTimeout(500)
// Wait for title to clear execution status
await titleMonitor.waitForIdleTitle()
// Check that execution status is cleared
const finishedTitle = await comfyPage.page.title()
expect(finishedTitle).toContain('ComfyUI')
expect(finishedTitle).not.toMatch(/\[\d+%\]/) // No percentage
expect(finishedTitle).not.toMatch(/\[\d+ nodes running\]/) // No running nodes
expect(finishedTitle).not.toContain('Executing')
})
})