mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
243
browser_tests/tests/sidebar/jobHistory.spec.ts
Normal file
243
browser_tests/tests/sidebar/jobHistory.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user