mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
add new progress bar tests
tidy tests, remove unused/unnecessary code
This commit is contained in:
@@ -64,4 +64,34 @@ export class OutputHistoryComponent {
|
||||
`[data-testid="${ids.historyItem}"][data-state="checked"]`
|
||||
)
|
||||
}
|
||||
|
||||
/** The header-level progress bar. */
|
||||
get headerProgressBar(): Locator {
|
||||
return this.page.getByTestId(ids.headerProgressBar)
|
||||
}
|
||||
|
||||
/** The in-progress item's progress bar (inside the thumbnail). */
|
||||
get itemProgressBar(): Locator {
|
||||
return this.inProgressItems.first().getByTestId(ids.itemProgressBar)
|
||||
}
|
||||
|
||||
/** Overall progress in the header bar. */
|
||||
get headerOverallProgress(): Locator {
|
||||
return this.headerProgressBar.getByTestId(ids.progressOverall)
|
||||
}
|
||||
|
||||
/** Node progress in the header bar. */
|
||||
get headerNodeProgress(): Locator {
|
||||
return this.headerProgressBar.getByTestId(ids.progressNode)
|
||||
}
|
||||
|
||||
/** Overall progress in the in-progress item bar. */
|
||||
get itemOverallProgress(): Locator {
|
||||
return this.itemProgressBar.getByTestId(ids.progressOverall)
|
||||
}
|
||||
|
||||
/** Node progress in the in-progress item bar. */
|
||||
get itemNodeProgress(): Locator {
|
||||
return this.itemProgressBar.getByTestId(ids.progressNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { WebSocketRoute } from '@playwright/test'
|
||||
|
||||
import type { RawJobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import type { ComfyPage } from '../ComfyPage'
|
||||
import type { MockWebSocket } from '../ws'
|
||||
import { createMockJob } from './AssetsHelper'
|
||||
|
||||
/**
|
||||
@@ -15,17 +16,13 @@ export class ExecutionHelper {
|
||||
|
||||
constructor(
|
||||
comfyPage: ComfyPage,
|
||||
private readonly mock: MockWebSocket
|
||||
private readonly ws: WebSocketRoute
|
||||
) {
|
||||
this.page = comfyPage.page
|
||||
this.command = comfyPage.command
|
||||
this.assets = comfyPage.assets
|
||||
}
|
||||
|
||||
private get ws() {
|
||||
return this.mock.ws
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept POST /api/prompt, execute Comfy.QueuePrompt, and return
|
||||
* the synthetic job ID.
|
||||
@@ -60,9 +57,6 @@ export class ExecutionHelper {
|
||||
await this.command.executeCommand('Comfy.QueuePrompt')
|
||||
await prompted
|
||||
|
||||
// Prevent real server events from interfering with test-controlled execution
|
||||
this.mock.stopServerForwarding()
|
||||
|
||||
return jobId
|
||||
}
|
||||
|
||||
@@ -108,6 +102,16 @@ export class ExecutionHelper {
|
||||
)
|
||||
}
|
||||
|
||||
/** Send `executing` WS event to signal which node is currently running. */
|
||||
executing(jobId: string, nodeId: string | null): void {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'executing',
|
||||
data: { prompt_id: jobId, node: nodeId }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/** Send `executed` WS event with node output. */
|
||||
executed(
|
||||
jobId: string,
|
||||
|
||||
@@ -138,7 +138,11 @@ export const TestIds = {
|
||||
latentPreview: 'linear-latent-preview',
|
||||
imageOutput: 'linear-image-output',
|
||||
videoOutput: 'linear-video-output',
|
||||
cancelRun: 'linear-cancel-run'
|
||||
cancelRun: 'linear-cancel-run',
|
||||
headerProgressBar: 'linear-header-progress-bar',
|
||||
itemProgressBar: 'linear-item-progress-bar',
|
||||
progressOverall: 'linear-progress-overall',
|
||||
progressNode: 'linear-progress-node'
|
||||
},
|
||||
appMode: {
|
||||
widgetItem: 'app-mode-widget-item'
|
||||
|
||||
@@ -1,41 +1,27 @@
|
||||
import { test as base } from '@playwright/test'
|
||||
import type { WebSocketRoute } from '@playwright/test'
|
||||
|
||||
export interface MockWebSocket {
|
||||
/** The intercepted WebSocket route. */
|
||||
ws: WebSocketRoute
|
||||
/** Stop forwarding real server messages to the page. */
|
||||
stopServerForwarding(): void
|
||||
}
|
||||
|
||||
export const webSocketFixture = base.extend<{
|
||||
getWebSocket: () => Promise<MockWebSocket>
|
||||
getWebSocket: () => Promise<WebSocketRoute>
|
||||
}>({
|
||||
getWebSocket: [
|
||||
async ({ context }, use) => {
|
||||
let latest: MockWebSocket | undefined
|
||||
let resolve: ((mock: MockWebSocket) => void) | undefined
|
||||
let latest: WebSocketRoute | undefined
|
||||
let resolve: ((ws: WebSocketRoute) => void) | undefined
|
||||
|
||||
await context.routeWebSocket(/\/ws/, (ws) => {
|
||||
let forwarding = true
|
||||
const server = ws.connectToServer()
|
||||
server.onMessage((message) => {
|
||||
if (forwarding) ws.send(message)
|
||||
ws.send(message)
|
||||
})
|
||||
|
||||
const mock: MockWebSocket = {
|
||||
ws,
|
||||
stopServerForwarding() {
|
||||
forwarding = false
|
||||
}
|
||||
}
|
||||
latest = mock
|
||||
resolve?.(mock)
|
||||
latest = ws
|
||||
resolve?.(ws)
|
||||
})
|
||||
|
||||
await use(() => {
|
||||
if (latest) return Promise.resolve(latest)
|
||||
return new Promise<MockWebSocket>((r) => {
|
||||
return new Promise<WebSocketRoute>((r) => {
|
||||
resolve = r
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ test.describe('Actionbar', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const { ws } = await getWebSocket()
|
||||
const ws = await getWebSocket()
|
||||
|
||||
// Enable change auto-queue mode
|
||||
const queueOpts = await comfyPage.actionbar.queueButton.toggleOptions()
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import type { WebSocketRoute } from '@playwright/test'
|
||||
import { mergeTests } from '@playwright/test'
|
||||
|
||||
import type { RawJobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { comfyPageFixture, comfyExpect as expect } from '../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '../fixtures/ws'
|
||||
import type { MockWebSocket } from '../fixtures/ws'
|
||||
import { ExecutionHelper } from '../fixtures/helpers/ExecutionHelper'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
// Node IDs from the default workflow (browser_tests/assets/default.json, 7 nodes)
|
||||
const SAVE_IMAGE_NODE = '9'
|
||||
const KSAMPLER_NODE = '3'
|
||||
const ALL_NODE_IDS = ['4', '6', '7', '5', KSAMPLER_NODE, '8', SAVE_IMAGE_NODE]
|
||||
|
||||
/** Queue a prompt, intercept it, and send execution_start. */
|
||||
async function startExecution(
|
||||
comfyPage: ComfyPage,
|
||||
mock: MockWebSocket,
|
||||
ws: WebSocketRoute,
|
||||
exec?: ExecutionHelper
|
||||
) {
|
||||
exec ??= new ExecutionHelper(comfyPage, mock)
|
||||
exec ??= new ExecutionHelper(comfyPage, ws)
|
||||
const jobId = await exec.run()
|
||||
// Allow storeJob() to complete before sending WS events
|
||||
await comfyPage.nextFrame()
|
||||
@@ -48,8 +50,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.skeletons.first()
|
||||
@@ -60,8 +62,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.skeletons.first()
|
||||
@@ -79,8 +81,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.inProgressItems.first()
|
||||
@@ -98,8 +100,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.inProgressItems.first()
|
||||
@@ -118,8 +120,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.inProgressItems.first()
|
||||
@@ -138,8 +140,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
const job: RawJobListItem = {
|
||||
id: jobId,
|
||||
@@ -176,8 +178,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
// Skeleton appears
|
||||
await expect(
|
||||
@@ -210,8 +212,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
// Skeleton is auto-selected
|
||||
await expect(
|
||||
@@ -226,7 +228,7 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
)
|
||||
).toHaveAttribute('src', /first\.png/)
|
||||
|
||||
// Second image arrives — selection auto-follows without user click
|
||||
// Second image arrives - selection auto-follows without user click
|
||||
exec.executed(jobId, SAVE_IMAGE_NODE, imageOutput('second.png'))
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.selectedInProgressItem.getByTestId(
|
||||
@@ -239,8 +241,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
// Send first image
|
||||
exec.executed(jobId, SAVE_IMAGE_NODE, imageOutput('first.png'))
|
||||
@@ -268,8 +270,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.inProgressItems.first()
|
||||
@@ -293,10 +295,10 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const ws = await getWebSocket()
|
||||
|
||||
// Complete one execution with 100 image outputs
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
exec.executed(
|
||||
jobId,
|
||||
SAVE_IMAGE_NODE,
|
||||
@@ -324,7 +326,7 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
await expect(firstItem).not.toBeInViewport()
|
||||
|
||||
// Start a new execution to get an in-progress item
|
||||
await startExecution(comfyPage, mock, exec)
|
||||
await startExecution(comfyPage, ws, exec)
|
||||
|
||||
// In-progress item is visible despite scrolling
|
||||
await expect(
|
||||
@@ -336,8 +338,8 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const mock = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, mock)
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
await expect(
|
||||
comfyPage.appMode.outputHistory.inProgressItems.first()
|
||||
@@ -347,4 +349,62 @@ test.describe('Output History', { tag: '@ui' }, () => {
|
||||
|
||||
await expect(comfyPage.appMode.outputHistory.inProgressItems).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('Progress bars update for both node and overall progress', async ({
|
||||
comfyPage,
|
||||
getWebSocket
|
||||
}) => {
|
||||
const ws = await getWebSocket()
|
||||
const { exec, jobId } = await startExecution(comfyPage, ws)
|
||||
|
||||
const {
|
||||
inProgressItems,
|
||||
headerOverallProgress,
|
||||
headerNodeProgress,
|
||||
itemOverallProgress,
|
||||
itemNodeProgress
|
||||
} = comfyPage.appMode.outputHistory
|
||||
|
||||
await expect(inProgressItems.first()).toBeVisible()
|
||||
|
||||
// Initially both bars are at 0%
|
||||
await expect(headerOverallProgress).toHaveAttribute('style', /width:\s*0%/)
|
||||
await expect(headerNodeProgress).toHaveAttribute('style', /width:\s*0%/)
|
||||
|
||||
// KSampler starts executing - node progress at 50%
|
||||
exec.executing(jobId, KSAMPLER_NODE)
|
||||
exec.progress(jobId, KSAMPLER_NODE, 5, 10)
|
||||
|
||||
await expect(headerNodeProgress).toHaveAttribute('style', /width:\s*50%/)
|
||||
await expect(itemNodeProgress).toHaveAttribute('style', /width:\s*50%/)
|
||||
// Overall still 0% - no nodes completed yet
|
||||
await expect(headerOverallProgress).toHaveAttribute('style', /width:\s*0%/)
|
||||
|
||||
// KSampler finishes - overall advances (1 of 7 nodes)
|
||||
exec.executed(jobId, KSAMPLER_NODE, {})
|
||||
|
||||
const oneNodePercent = Math.round((1 / ALL_NODE_IDS.length) * 100)
|
||||
const pct = new RegExp(`width:\\s*${oneNodePercent}%`)
|
||||
await expect(headerOverallProgress).toHaveAttribute('style', pct)
|
||||
await expect(itemOverallProgress).toHaveAttribute('style', pct)
|
||||
|
||||
// Node progress reaches 100%
|
||||
exec.progress(jobId, KSAMPLER_NODE, 10, 10)
|
||||
|
||||
await expect(headerNodeProgress).toHaveAttribute('style', /width:\s*100%/)
|
||||
await expect(itemNodeProgress).toHaveAttribute('style', /width:\s*100%/)
|
||||
|
||||
// Complete remaining nodes - overall reaches 100%
|
||||
const remainingNodes = ALL_NODE_IDS.filter((id) => id !== KSAMPLER_NODE)
|
||||
for (const nodeId of remainingNodes) {
|
||||
exec.executing(jobId, nodeId)
|
||||
exec.executed(jobId, nodeId, {})
|
||||
}
|
||||
|
||||
await expect(headerOverallProgress).toHaveAttribute(
|
||||
'style',
|
||||
/width:\s*100%/
|
||||
)
|
||||
await expect(itemOverallProgress).toHaveAttribute('style', /width:\s*100%/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,6 +22,7 @@ const executionStore = useExecutionStore()
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-2 bg-secondary-background transition-opacity',
|
||||
@@ -32,11 +33,13 @@ const executionStore = useExecutionStore()
|
||||
"
|
||||
>
|
||||
<div
|
||||
data-testid="linear-progress-overall"
|
||||
class="absolute inset-0 h-full bg-interface-panel-job-progress-primary transition-[width]"
|
||||
:class="cn(rounded && 'rounded-sm')"
|
||||
:style="{ width: `${totalPercent}%`, opacity: overallOpacity }"
|
||||
/>
|
||||
<div
|
||||
data-testid="linear-progress-node"
|
||||
class="absolute inset-0 h-full bg-interface-panel-job-progress-secondary transition-[width]"
|
||||
:class="cn(rounded && 'rounded-sm')"
|
||||
:style="{ width: `${currentNodePercent}%`, opacity: activeOpacity }"
|
||||
|
||||
@@ -18,6 +18,10 @@ const { latentPreview } = defineProps<{
|
||||
data-testid="linear-skeleton"
|
||||
class="skeleton-shimmer size-10 rounded-sm"
|
||||
/>
|
||||
<LinearProgressBar class="mt-1 h-1 w-10" rounded />
|
||||
<LinearProgressBar
|
||||
data-testid="linear-item-progress-bar"
|
||||
class="mt-1 h-1 w-10"
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -154,6 +154,7 @@ function dragDrop(e: DragEvent) {
|
||||
@drop="dragDrop"
|
||||
>
|
||||
<LinearProgressBar
|
||||
data-testid="linear-header-progress-bar"
|
||||
class="absolute top-0 left-0 z-21 h-1 w-[calc(100%+16px)]"
|
||||
/>
|
||||
<LinearPreview
|
||||
|
||||
Reference in New Issue
Block a user