mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 17:10:07 +00:00
Add tests
This commit is contained in:
@@ -13,6 +13,7 @@ import { ComfyTemplates } from '../helpers/templates'
|
||||
import { ComfyMouse } from './ComfyMouse'
|
||||
import { VueNodeHelpers } from './VueNodeHelpers'
|
||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
import { QueueList } from './components/QueueList'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
import {
|
||||
NodeLibrarySidebarTab,
|
||||
@@ -151,6 +152,7 @@ export class ComfyPage {
|
||||
|
||||
// Components
|
||||
public readonly searchBox: ComfyNodeSearchBox
|
||||
public readonly queueList: QueueList
|
||||
public readonly menu: ComfyMenu
|
||||
public readonly actionbar: ComfyActionbar
|
||||
public readonly templates: ComfyTemplates
|
||||
@@ -183,6 +185,7 @@ export class ComfyPage {
|
||||
this.visibleToasts = page.locator('.p-toast-message:visible')
|
||||
|
||||
this.searchBox = new ComfyNodeSearchBox(page)
|
||||
this.queueList = new QueueList(page)
|
||||
this.menu = new ComfyMenu(page)
|
||||
this.actionbar = new ComfyActionbar(page)
|
||||
this.templates = new ComfyTemplates(page)
|
||||
|
||||
57
browser_tests/fixtures/components/QueueList.ts
Normal file
57
browser_tests/fixtures/components/QueueList.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
export class QueueList {
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
get toggleButton() {
|
||||
return this.page.getByTestId('queue-toggle-button')
|
||||
}
|
||||
|
||||
get inlineProgress() {
|
||||
return this.page.getByTestId('queue-inline-progress')
|
||||
}
|
||||
|
||||
get overlay() {
|
||||
return this.page.getByTestId('queue-overlay')
|
||||
}
|
||||
|
||||
get closeButton() {
|
||||
return this.page.getByTestId('queue-overlay-close-button')
|
||||
}
|
||||
|
||||
get jobItems() {
|
||||
return this.page.getByTestId('queue-job-item')
|
||||
}
|
||||
|
||||
get clearHistoryButton() {
|
||||
return this.page.getByRole('button', { name: /Clear History/i })
|
||||
}
|
||||
|
||||
async open() {
|
||||
if (!(await this.overlay.isVisible())) {
|
||||
await this.toggleButton.click()
|
||||
await expect(this.overlay).toBeVisible()
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (await this.overlay.isVisible()) {
|
||||
await this.closeButton.click()
|
||||
await expect(this.overlay).not.toBeVisible()
|
||||
}
|
||||
}
|
||||
|
||||
async getJobCount(state?: string) {
|
||||
if (state) {
|
||||
return await this.page
|
||||
.locator(`[data-testid="queue-job-item"][data-job-state="${state}"]`)
|
||||
.count()
|
||||
}
|
||||
return await this.jobItems.count()
|
||||
}
|
||||
|
||||
getJobAction(actionKey: string) {
|
||||
return this.page.getByTestId(`job-action-${actionKey}`)
|
||||
}
|
||||
}
|
||||
156
browser_tests/tests/queue/queueList.spec.ts
Normal file
156
browser_tests/tests/queue/queueList.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { expect, mergeTests } from '@playwright/test'
|
||||
|
||||
import type { ComfyPage } from '../../fixtures/ComfyPage'
|
||||
import type { StatusWsMessage } from '../../../src/schemas/apiSchema.ts'
|
||||
import { comfyPageFixture } from '../../fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '../../fixtures/ws'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
type QueueState = {
|
||||
running: QueueJob[]
|
||||
pending: QueueJob[]
|
||||
}
|
||||
|
||||
type QueueJob = [
|
||||
string,
|
||||
string,
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>,
|
||||
string[]
|
||||
]
|
||||
|
||||
type QueueController = {
|
||||
state: QueueState
|
||||
sync: (
|
||||
ws: { trigger(data: any, url?: string): Promise<void> },
|
||||
nextState: Partial<QueueState>
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
test.describe('Queue UI', () => {
|
||||
let queue: QueueController
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.page.route('**/api/prompt', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
prompt_id: 'mock-prompt-id',
|
||||
number: 1,
|
||||
node_errors: {}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Mock history to avoid pulling real data
|
||||
await comfyPage.page.route('**/api/history**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ History: [] })
|
||||
})
|
||||
})
|
||||
|
||||
queue = await createQueueController(comfyPage)
|
||||
})
|
||||
|
||||
test('toggles overlay and updates count from status events', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await queue.sync(ws, { running: [], pending: [] })
|
||||
|
||||
await expect(comfyPage.queueList.toggleButton).toBeVisible()
|
||||
await expect(comfyPage.queueList.toggleButton).toHaveText(/0 queued/i)
|
||||
await expect(comfyPage.queueList.overlay).toBeHidden()
|
||||
|
||||
await queue.sync(ws, {
|
||||
pending: [queueJob('1', 'mock-pending', 'client-a')]
|
||||
})
|
||||
|
||||
await expect(comfyPage.queueList.toggleButton).toHaveText(/1 queued/i)
|
||||
|
||||
await comfyPage.queueList.open()
|
||||
await expect(comfyPage.queueList.overlay).toBeVisible()
|
||||
await expect(comfyPage.queueList.jobItems).toHaveCount(1)
|
||||
|
||||
await comfyPage.queueList.close()
|
||||
await expect(comfyPage.queueList.overlay).toBeHidden()
|
||||
})
|
||||
|
||||
test('displays running and pending jobs via status updates', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await queue.sync(ws, {
|
||||
running: [queueJob('2', 'mock-running', 'client-b')],
|
||||
pending: [queueJob('3', 'mock-pending', 'client-c')]
|
||||
})
|
||||
|
||||
await comfyPage.queueList.open()
|
||||
await expect(comfyPage.queueList.jobItems).toHaveCount(2)
|
||||
|
||||
const firstJob = comfyPage.queueList.jobItems.first()
|
||||
await firstJob.hover()
|
||||
|
||||
const cancelAction = firstJob
|
||||
.getByTestId('job-action-cancel-running')
|
||||
.or(firstJob.getByTestId('job-action-cancel-hover'))
|
||||
|
||||
await expect(cancelAction).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
const queueJob = (
|
||||
queueIndex: string,
|
||||
promptId: string,
|
||||
clientId: string
|
||||
): QueueJob => [
|
||||
queueIndex,
|
||||
promptId,
|
||||
{ client_id: clientId },
|
||||
{ class_type: 'Note' },
|
||||
['output']
|
||||
]
|
||||
|
||||
const createQueueController = async (
|
||||
comfyPage: ComfyPage
|
||||
): Promise<QueueController> => {
|
||||
const state: QueueState = { running: [], pending: [] }
|
||||
|
||||
// Single queue handler reads the latest in-memory state
|
||||
await comfyPage.page.route('**/api/queue', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
queue_running: state.running,
|
||||
queue_pending: state.pending
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const sync = async (
|
||||
ws: { trigger(data: any, url?: string): Promise<void> },
|
||||
nextState: Partial<QueueState>
|
||||
) => {
|
||||
if (nextState.running) state.running = nextState.running
|
||||
if (nextState.pending) state.pending = nextState.pending
|
||||
|
||||
const total = state.running.length + state.pending.length
|
||||
const queueResponse = comfyPage.page.waitForResponse('**/api/queue')
|
||||
|
||||
await ws.trigger({
|
||||
type: 'status',
|
||||
data: {
|
||||
status: { exec_info: { queue_remaining: total } }
|
||||
}
|
||||
} as StatusWsMessage)
|
||||
|
||||
await queueResponse
|
||||
}
|
||||
|
||||
return { state, sync }
|
||||
}
|
||||
@@ -31,7 +31,10 @@
|
||||
>
|
||||
<i class="icon-[lucide--panel-right] size-4" />
|
||||
</IconButton>
|
||||
<QueueInlineProgress :hidden="isQueueOverlayExpanded" />
|
||||
<QueueInlineProgress
|
||||
:hidden="isQueueOverlayExpanded"
|
||||
data-testid="queue-inline-progress"
|
||||
/>
|
||||
</div>
|
||||
<QueueProgressOverlay v-model:expanded="isQueueOverlayExpanded" />
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
size="sm"
|
||||
class="size-6 bg-transparent hover:bg-secondary-background hover:opacity-100"
|
||||
:aria-label="t('g.close')"
|
||||
data-testid="queue-overlay-close-button"
|
||||
@click="onCloseClick"
|
||||
>
|
||||
<i
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<div
|
||||
class="pointer-events-auto flex w-[350px] min-w-[310px] max-h-[60vh] flex-col overflow-hidden rounded-lg border font-inter transition-colors duration-200 ease-in-out"
|
||||
:class="containerClass"
|
||||
data-testid="queue-overlay"
|
||||
>
|
||||
<!-- Expanded state -->
|
||||
<QueueOverlayExpanded
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<div
|
||||
ref="rowRef"
|
||||
class="relative"
|
||||
data-testid="queue-job-item"
|
||||
:data-job-id="props.jobId"
|
||||
:data-job-state="props.state"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@contextmenu.stop.prevent="onContextMenu"
|
||||
@@ -117,6 +120,7 @@
|
||||
size="sm"
|
||||
:class="getActionButtonClass()"
|
||||
:aria-label="action.ariaLabel"
|
||||
:data-testid="`job-action-${action.key}`"
|
||||
@click.stop="action.onClick?.($event)"
|
||||
>
|
||||
<i :class="cn(action.iconClass, 'size-4')" />
|
||||
@@ -127,6 +131,7 @@
|
||||
type="transparent"
|
||||
:label="action.label"
|
||||
:aria-label="action.ariaLabel"
|
||||
:data-testid="`job-action-${action.key}`"
|
||||
@click.stop="action.onClick?.($event)"
|
||||
/>
|
||||
</template>
|
||||
@@ -141,6 +146,7 @@
|
||||
size="sm"
|
||||
:class="getActionButtonClass()"
|
||||
:aria-label="action.ariaLabel"
|
||||
:data-testid="`job-action-${action.key}`"
|
||||
@click.stop="action.onClick?.($event)"
|
||||
>
|
||||
<i :class="cn(action.iconClass, 'size-4')" />
|
||||
@@ -151,6 +157,7 @@
|
||||
type="transparent"
|
||||
:label="action.label"
|
||||
:aria-label="action.ariaLabel"
|
||||
:data-testid="`job-action-${action.key}`"
|
||||
@click.stop="action.onClick?.($event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user