Compare commits

...

8 Commits

Author SHA1 Message Date
Christian Byrne
d56d47d874 Merge branch 'main' into glary/workflow-extraction-test-infra 2026-05-04 14:33:04 -07:00
Glary-Bot
731187b1f5 test(infra): remove redundant docblock on createJobDetailFixture
Per repo guideline (avoid redundant comments), the function/option
names and the spread shape are self-documenting.
2026-05-03 01:09:07 -07:00
Glary-Bot
46439f5a6f test(infra): remove redundant docblock on MINIMAL_WORKFLOW
Per repo guideline (avoid redundant comments), the symbol name plus
'satisfies ComfyWorkflowJSON' annotation already convey intent.
2026-05-03 01:09:07 -07:00
Glary-Bot
2c561fb0d1 test(infra): align mock view-route type default and lock id on overrides
- createJobDetailFixture: omit 'id' from overrides as well, so callers
  can't reintroduce the desync between id and preview_output.filename.
- AssetsHelper.mockInputAssetFile: align lookup default for type with
  the registration default ('input') so requests that omit the type
  param don't silently miss input registrations.
2026-05-03 01:09:07 -07:00
Glary-Bot
0a84302f0d test(infra): move createJobDetailFixture to helpers/ (data/ is for static data only)
browser_tests/fixtures/data/ is reserved for static payloads; the
builder function was violating that convention. Moved it to
fixtures/helpers/jobFixtures.ts and kept only MINIMAL_WORKFLOW
(static) in fixtures/data/jobFixtures.ts.
2026-05-03 01:09:07 -07:00
Glary-Bot
6c9f1d3045 test(infra): address coderabbit review - guard job fixtures and mock handlers
- jobFixtures: Omit 'id' from jobOverrides to prevent desync with preview_output.filename
- AssetsHelper: gate jobDetail and view route handlers to GET only (fallback on others)
- AssetsHelper: validate mockInputAssetFile receives exactly one of 'path' or 'body'
2026-05-03 01:09:07 -07:00
Glary-Bot
665d462ebe test(infra): address review - match view mock on filename+type+subfolder, fall through unmatched, add ComfyWorkflowJSON satisfies 2026-05-03 01:08:35 -07:00
Glary-Bot
82bef71794 test(infra): add JobDetail fixtures and view/job-detail mocks for AssetsHelper
Prereq infrastructure for workflowExtractionUtil.ts e2e coverage.

- createJobDetailFixture() wraps workflow at workflow.extra_data.extra_pnginfo.workflow

- AssetsHelper.mockJobDetail(jobId, jobDetail) intercepts GET /api/jobs/:id

- AssetsHelper.mockInputAssetFile(filename, file) intercepts GET /api/view?filename=

- clearMocks() unroutes the new handlers
2026-05-03 01:08:35 -07:00
3 changed files with 153 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
export const MINIMAL_WORKFLOW = {
last_node_id: 0,
last_link_id: 0,
nodes: [],
links: [],
groups: [],
config: {},
extra: {},
version: 0.4
} satisfies ComfyWorkflowJSON

View File

@@ -15,6 +15,7 @@ const jobsListRoutePattern = '**/api/jobs?*'
const assetsListRoutePattern = /\/api\/assets(?:\?.*)?$/
const assetExportRoutePattern = '**/api/assets/export'
const inputFilesRoutePattern = /\/internal\/files\/input(?:\?.*)?$/
const viewRoutePattern = /\/api\/view(?:\?.*)?$/
const historyRoutePattern = /\/api\/history$/
/**
@@ -174,6 +175,7 @@ export class AssetsHelper {
null
private inputFilesRouteHandler: ((route: Route) => Promise<void>) | null =
null
private viewRouteHandler: ((route: Route) => Promise<void>) | null = null
private deleteHistoryRouteHandler: ((route: Route) => Promise<void>) | null =
null
private generatedJobs: RawJobListItem[] = []
@@ -185,6 +187,18 @@ export class AssetsHelper {
string,
(route: Route) => Promise<void>
>()
private readonly inputAssetFiles = new Map<
string,
{ path?: string; body?: Buffer; contentType?: string }
>()
private static buildAssetFileKey(
filename: string,
type: 'input' | 'output',
subfolder: string
): string {
return `${type}::${subfolder}::${filename}`
}
constructor(private readonly page: Page) {}
@@ -355,6 +369,78 @@ export class AssetsHelper {
await this.page.route(inputFilesRoutePattern, this.inputFilesRouteHandler)
}
/**
* Intercepts `GET /api/view?filename=...&type=...[&subfolder=...]` and
* serves a real file. Matches on `filename` + `type` + `subfolder` to
* mirror `getAssetUrl()`; requests that don't match a registered entry
* fall through so unrelated preview/image loads keep working.
*/
async mockInputAssetFile(
filename: string,
file: {
path?: string
body?: Buffer
contentType?: string
type?: 'input' | 'output'
subfolder?: string
}
): Promise<void> {
const hasPath = typeof file.path === 'string'
const hasBody = file.body !== undefined
if (hasPath === hasBody) {
throw new Error(
'mockInputAssetFile expects exactly one of "path" or "body"'
)
}
const type = file.type ?? 'input'
const subfolder = file.subfolder ?? ''
const key = AssetsHelper.buildAssetFileKey(filename, type, subfolder)
this.inputAssetFiles.set(key, {
path: file.path,
body: file.body,
contentType: file.contentType
})
if (this.viewRouteHandler) return
this.viewRouteHandler = async (route: Route) => {
if (route.request().method() !== 'GET') {
await route.fallback()
return
}
const url = new URL(route.request().url())
const requestedName = url.searchParams.get('filename')
const requestedType = url.searchParams.get('type') ?? 'input'
const requestedSubfolder = url.searchParams.get('subfolder') ?? ''
const entry =
requestedName &&
(requestedType === 'input' || requestedType === 'output')
? this.inputAssetFiles.get(
AssetsHelper.buildAssetFileKey(
requestedName,
requestedType,
requestedSubfolder
)
)
: undefined
if (!entry) {
await route.fallback()
return
}
await route.fulfill({
status: 200,
...(entry.contentType && { contentType: entry.contentType }),
...(entry.path ? { path: entry.path } : { body: entry.body ?? '' })
})
}
await this.page.route(viewRoutePattern, this.viewRouteHandler)
}
/**
* Mock the POST /api/history endpoint used for deleting history items.
* On receiving a `{ delete: [id] }` payload, removes matching jobs from
@@ -396,6 +482,7 @@ export class AssetsHelper {
this.assetExportRequests = []
this.assetExportResponse = null
this.importedFiles = []
this.inputAssetFiles.clear()
if (this.jobsRouteHandler) {
await this.page.unroute(jobsListRoutePattern, this.jobsRouteHandler)
@@ -426,6 +513,11 @@ export class AssetsHelper {
this.inputFilesRouteHandler = null
}
if (this.viewRouteHandler) {
await this.page.unroute(viewRoutePattern, this.viewRouteHandler)
this.viewRouteHandler = null
}
if (this.deleteHistoryRouteHandler) {
await this.page.unroute(
historyRoutePattern,

View File

@@ -0,0 +1,49 @@
import type {
JobDetail,
RawJobListItem
} from '@/platform/remote/comfyui/jobs/jobTypes'
export interface CreateJobDetailFixtureOptions {
id?: string
workflow?: unknown
overrides?: Omit<Partial<JobDetail>, 'id'>
jobOverrides?: Omit<Partial<RawJobListItem>, 'id'>
}
const DEFAULT_JOB_ID = 'job-detail-001'
export function createJobDetailFixture({
id = DEFAULT_JOB_ID,
workflow,
overrides,
jobOverrides
}: CreateJobDetailFixtureOptions = {}): JobDetail {
const now = Date.now()
const base: JobDetail = {
id,
status: 'completed',
create_time: now,
execution_start_time: now,
execution_end_time: now + 5_000,
preview_output: {
filename: `output_${id}.png`,
subfolder: '',
type: 'output',
nodeId: '1',
mediaType: 'images'
},
outputs_count: 1,
priority: 0,
...jobOverrides
}
const detail: JobDetail = {
...base,
...(workflow !== undefined && {
workflow: { extra_data: { extra_pnginfo: { workflow } } }
}),
...overrides
}
return detail
}