add new progress bar tests

tidy tests, remove unused/unnecessary code
This commit is contained in:
pythongosssss
2026-04-08 05:38:47 -07:00
parent 4492e3f2d6
commit ab4a00d6dc
9 changed files with 154 additions and 62 deletions

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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'

View File

@@ -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
})
})

View File

@@ -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()

View File

@@ -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%/)
})
})

View File

@@ -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 }"

View File

@@ -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>

View File

@@ -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