Compare commits

...

4 Commits

Author SHA1 Message Date
dante01yoon
725e4eabb3 fix(test): enable QPOV2 setting so job history sidebar tab is registered
The job history sidebar tab is only rendered when Comfy.Queue.QPOV2 is
true (default: false). All 11 tests timed out waiting for the tab button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:47:04 +09:00
dante01yoon
7e3fb71284 fix(test): use seconds-based timestamps to match main convention
Align createMockJob defaults and jobHistory test data with main's
seconds-based timestamp convention (Date.now() / 1000).
2026-04-01 15:00:42 +09:00
dante01yoon
58dcfbc983 fix(test): scope job history locators and use millisecond timestamps
- Scope all JobHistorySidebarTab locators to .sidebar-content-container
  to avoid collision with QueueOverlayExpanded's identical controls
- Use milliseconds (Date.now()) instead of seconds for mock timestamps,
  matching the Python backend's int(time.time() * 1000) format
2026-04-01 14:59:27 +09:00
dante01yoon
1a01192140 test(jobHistory): add E2E tests for job history sidebar tab
Add JobHistorySidebarTab fixture with filter tab, search, and job
item locators. Add createMockJob factory to AssetsHelper. Add 11
test scenarios covering tab display, filter tabs (All/Completed/
Failed), search, empty state, and Failed tab visibility.
2026-04-01 14:59:27 +09:00
3 changed files with 314 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ import { QueuePanel } from '@e2e/fixtures/components/QueuePanel'
import { SettingDialog } from '@e2e/fixtures/components/SettingDialog'
import {
AssetsSidebarTab,
JobHistorySidebarTab,
NodeLibrarySidebarTab,
WorkflowsSidebarTab
} from '@e2e/fixtures/components/SidebarTab'
@@ -55,6 +56,7 @@ class ComfyPropertiesPanel {
class ComfyMenu {
private _assetsTab: AssetsSidebarTab | null = null
private _jobHistoryTab: JobHistorySidebarTab | null = null
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
private _workflowsTab: WorkflowsSidebarTab | null = null
private _topbar: Topbar | null = null
@@ -73,6 +75,11 @@ class ComfyMenu {
return this.sideToolbar.locator('.side-bar-button')
}
get jobHistoryTab() {
this._jobHistoryTab ??= new JobHistorySidebarTab(this.page)
return this._jobHistoryTab
}
get nodeLibraryTab() {
this._nodeLibraryTab ??= new NodeLibrarySidebarTab(this.page)
return this._nodeLibraryTab

View File

@@ -170,6 +170,63 @@ export class WorkflowsSidebarTab extends SidebarTab {
}
}
export class JobHistorySidebarTab extends SidebarTab {
constructor(public override readonly page: Page) {
super(page, 'job-history')
}
/** Scope all locators to the sidebar panel to avoid collision
* with QueueOverlayExpanded which renders the same controls. */
private get panel() {
return this.page.locator('.sidebar-content-container')
}
get allTab() {
return this.panel.getByRole('button', { name: 'All', exact: true })
}
get completedTab() {
return this.panel.getByRole('button', { name: 'Completed', exact: true })
}
get failedTab() {
return this.panel.getByRole('button', { name: 'Failed', exact: true })
}
get searchInput() {
return this.panel.getByPlaceholder('Search...')
}
get filterButton() {
return this.panel.getByRole('button', { name: /Filter/i })
}
get sortButton() {
return this.panel.getByRole('button', { name: /Sort/i })
}
get jobItems() {
return this.panel.locator('[data-job-id]')
}
get noActiveJobsText() {
return this.panel.getByText('No active jobs')
}
getJobById(id: string) {
return this.panel.locator(`[data-job-id="${id}"]`)
}
get groupLabels() {
return this.panel.locator('.text-xs.text-text-secondary')
}
override async open() {
await super.open()
await this.allTab.waitFor({ state: 'visible', timeout: 5000 })
}
}
export class AssetsSidebarTab extends SidebarTab {
constructor(public override readonly page: Page) {
super(page, 'assets')

View File

@@ -0,0 +1,250 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
import { createMockJob } from '../../fixtures/helpers/AssetsHelper'
import type { RawJobListItem } from '../../../src/platform/remote/comfyui/jobs/jobTypes'
const now = Date.now() / 1000
const COMPLETED_JOBS: RawJobListItem[] = [
createMockJob({
id: 'job-completed-1',
status: 'completed',
create_time: now - 60,
execution_start_time: now - 60,
execution_end_time: now - 50,
outputs_count: 2
}),
createMockJob({
id: 'job-completed-2',
status: 'completed',
create_time: now - 120,
execution_start_time: now - 120,
execution_end_time: now - 115,
outputs_count: 1
})
]
const FAILED_JOBS: RawJobListItem[] = [
createMockJob({
id: 'job-failed-1',
status: 'failed',
create_time: now - 30,
execution_start_time: now - 30,
execution_end_time: now - 28,
outputs_count: 0
})
]
const ALL_JOBS = [...COMPLETED_JOBS, ...FAILED_JOBS]
// The job history sidebar tab uses the same /api/jobs endpoint as
// the assets tab. Mocks are set up via comfyPage.assets which
// intercepts /api/jobs and handles status/offset/limit query params.
// ==========================================================================
// 1. Tab open and job display
// ==========================================================================
test.describe('Job history sidebar - display', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.assets.mockOutputHistory(ALL_JOBS)
await comfyPage.settings.setSetting('Comfy.Queue.QPOV2', true)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.assets.clearMocks()
})
test('Opens job history tab and shows job items', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
await expect
.poll(() => tab.jobItems.count(), { timeout: 5000 })
.toBeGreaterThanOrEqual(1)
})
test('Shows All, Completed filter tabs', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.allTab).toBeVisible()
await expect(tab.completedTab).toBeVisible()
})
test('Shows Failed tab when failed jobs exist', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
// Wait for job items to render (store needs to process the data)
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
await expect(tab.failedTab).toBeVisible()
})
test('Shows search input and filter/sort buttons', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.searchInput).toBeVisible()
await expect(tab.filterButton).toBeVisible()
await expect(tab.sortButton).toBeVisible()
})
})
// ==========================================================================
// 2. Filter tabs
// ==========================================================================
test.describe('Job history sidebar - filter tabs', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.assets.mockOutputHistory(ALL_JOBS)
await comfyPage.settings.setSetting('Comfy.Queue.QPOV2', true)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.assets.clearMocks()
})
test('Completed tab filters to completed jobs only', async ({
comfyPage
}) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
await tab.completedTab.click()
// Should show completed jobs
await expect(tab.getJobById('job-completed-1')).toBeVisible({
timeout: 5000
})
// Failed job should not be visible
await expect(tab.getJobById('job-failed-1')).not.toBeVisible()
})
test('Failed tab filters to failed jobs only', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
await tab.failedTab.click()
await expect(tab.getJobById('job-failed-1')).toBeVisible({ timeout: 5000 })
await expect(tab.getJobById('job-completed-1')).not.toBeVisible()
})
test('All tab shows all jobs again', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
// Switch to Completed then back to All
await tab.completedTab.click()
await expect(tab.getJobById('job-failed-1')).not.toBeVisible()
await tab.allTab.click()
await expect(tab.getJobById('job-completed-1')).toBeVisible({
timeout: 5000
})
await expect(tab.getJobById('job-failed-1')).toBeVisible()
})
})
// ==========================================================================
// 3. Search
// ==========================================================================
test.describe('Job history sidebar - search', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.assets.mockOutputHistory(ALL_JOBS)
await comfyPage.settings.setSetting('Comfy.Queue.QPOV2', true)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.assets.clearMocks()
})
test('Search filters jobs by text', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
const initialCount = await tab.jobItems.count()
// Search for a specific job ID substring
await tab.searchInput.fill('failed')
// Wait for filter to reduce count (150ms debounce)
await expect(async () => {
const count = await tab.jobItems.count()
expect(count).toBeLessThan(initialCount)
}).toPass({ timeout: 5000 })
})
})
// ==========================================================================
// 4. Empty state
// ==========================================================================
test.describe('Job history sidebar - empty state', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.assets.mockOutputHistory([])
await comfyPage.settings.setSetting('Comfy.Queue.QPOV2', true)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.assets.clearMocks()
})
test('Shows no active jobs when history is empty', async ({ comfyPage }) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.noActiveJobsText).toBeVisible()
expect(await tab.jobItems.count()).toBe(0)
})
test('Failed tab is hidden when no failed jobs exist', async ({
comfyPage
}) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.failedTab).not.toBeVisible()
})
})
// ==========================================================================
// 5. Only completed jobs (no failed tab)
// ==========================================================================
test.describe('Job history sidebar - completed only', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.assets.mockOutputHistory(COMPLETED_JOBS)
await comfyPage.settings.setSetting('Comfy.Queue.QPOV2', true)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.assets.clearMocks()
})
test('Failed tab hidden when only completed jobs exist', async ({
comfyPage
}) => {
const tab = comfyPage.menu.jobHistoryTab
await tab.open()
await expect(tab.jobItems.first()).toBeVisible({ timeout: 5000 })
await expect(tab.failedTab).not.toBeVisible()
await expect(tab.allTab).toBeVisible()
await expect(tab.completedTab).toBeVisible()
})
})