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.
This commit is contained in:
dante01yoon
2026-04-01 13:31:17 +09:00
parent df42b7a2a8
commit 1a01192140
3 changed files with 303 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,59 @@ export class WorkflowsSidebarTab extends SidebarTab {
}
}
export class JobHistorySidebarTab extends SidebarTab {
constructor(public override readonly page: Page) {
super(page, 'job-history')
}
get allTab() {
return this.page.getByRole('button', { name: 'All', exact: true })
}
get completedTab() {
return this.page.getByRole('button', { name: 'Completed', exact: true })
}
get failedTab() {
return this.page.getByRole('button', { name: 'Failed', exact: true })
}
get searchInput() {
return this.page.getByPlaceholder('Search...')
}
get filterButton() {
return this.page.getByRole('button', { name: /Filter/i })
}
get sortButton() {
return this.page.getByRole('button', { name: /Sort/i })
}
get jobItems() {
return this.page.locator('[data-job-id]')
}
get noActiveJobsText() {
return this.page.getByText('No active jobs')
}
getJobById(id: string) {
return this.page.locator(`[data-job-id="${id}"]`)
}
get groupLabels() {
return this.page.locator(
'.sidebar-content-container .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,243 @@
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 COMPLETED_JOBS: RawJobListItem[] = [
createMockJob({
id: 'job-completed-1',
status: 'completed',
create_time: Date.now() / 1000 - 60,
execution_start_time: Date.now() / 1000 - 60,
execution_end_time: Date.now() / 1000 - 50,
outputs_count: 2
}),
createMockJob({
id: 'job-completed-2',
status: 'completed',
create_time: Date.now() / 1000 - 120,
execution_start_time: Date.now() / 1000 - 120,
execution_end_time: Date.now() / 1000 - 115,
outputs_count: 1
})
]
const FAILED_JOBS: RawJobListItem[] = [
createMockJob({
id: 'job-failed-1',
status: 'failed',
create_time: Date.now() / 1000 - 30,
execution_start_time: Date.now() / 1000 - 30,
execution_end_time: Date.now() / 1000 - 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.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.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.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.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.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()
})
})