mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-19 20:09:42 +00:00
## Summary Backfills missing e2e test coverage identified in the [FixIt Burndown](https://www.notion.so/comfy-org/FixIt-Burndown-32e6d73d365080609a81cdc9bc884460) audit. Adds 39 new behavioral tests across 5 spec files with zero test-code overlap. ## Changes - **What**: New e2e specs for Image Crop (6 tests) and Curve Widget (6 tests). Deepened coverage for Minimap (+6), Mask Editor (+10), Painter (+11). - **New fixtures**: `curve_widget.json`, updated `image_crop_widget.json` ## Test Inventory | Spec | New tests | Coverage area | |---|---|---| | `imageCrop.spec.ts` | 6 | Empty state, bounding box inputs, ratio selector/presets, lock toggle, programmatic value update | | `curveWidget.spec.ts` | 6 | SVG render, click-to-add point, drag-to-reshape, Ctrl+click remove, interpolation mode switch, min-2 guard | | `minimap.spec.ts` | +6 | Click-to-pan, drag-to-pan, zoom viewport shrink, node count changes, workflow reload, pan state reflection | | `maskEditor.spec.ts` | +10 | Brush drawing, undo/redo, clear, cancel, invert, Ctrl+Z, tool panel/switching, brush settings, save with mock, eraser | | `painter.spec.ts` | +11 | Clear, eraser, control visibility toggle, brush size slider, stroke width comparison, canvas dimensions, background color, multi-stroke accumulate, color picker, opacity, partial erase | ## Review Focus - Mask editor tests use `.maskEditor_toolPanelContainer` class selectors — may need test-id hardening later - Painter slider interaction tests could be flaky if slider layout changes - All canvas pixel-count assertions use `expect.poll()` with timeouts for reliability ## Test plan - [ ] CI passes all new/modified specs - [ ] No duplicate coverage with existing tests (verified via grep before writing) - [ ] No `waitForTimeout` usage (confirmed) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11183-test-backfill-e2e-coverage-gaps-for-toolkit-widgets-minimap-mask-editor-painter-3416d73d3650819ca33edd1f27b9651a) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import type { Locator, Page } from '@playwright/test'
|
|
import type { TestGraphAccess } from '@e2e/types/globals'
|
|
|
|
export async function drawStroke(
|
|
page: Page,
|
|
canvas: Locator,
|
|
opts: { startXPct?: number; endXPct?: number; yPct?: number } = {}
|
|
): Promise<void> {
|
|
const { startXPct = 0.3, endXPct = 0.7, yPct = 0.5 } = opts
|
|
const box = await canvas.boundingBox()
|
|
if (!box) throw new Error('Canvas bounding box not found')
|
|
await page.mouse.move(
|
|
box.x + box.width * startXPct,
|
|
box.y + box.height * yPct
|
|
)
|
|
await page.mouse.down()
|
|
await page.mouse.move(
|
|
box.x + box.width * endXPct,
|
|
box.y + box.height * yPct,
|
|
{ steps: 10 }
|
|
)
|
|
await page.mouse.up()
|
|
}
|
|
|
|
export async function hasCanvasContent(canvas: Locator): Promise<boolean> {
|
|
return canvas.evaluate((el: HTMLCanvasElement) => {
|
|
const ctx = el.getContext('2d')
|
|
if (!ctx) return false
|
|
const { data } = ctx.getImageData(0, 0, el.width, el.height)
|
|
for (let i = 3; i < data.length; i += 4) {
|
|
if (data[i] > 0) return true
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
export async function triggerSerialization(page: Page): Promise<void> {
|
|
await page.waitForFunction(() => {
|
|
const graph = window.graph as TestGraphAccess | undefined
|
|
const node = graph?._nodes_by_id?.['1']
|
|
const widget = node?.widgets?.find((w) => w.name === 'mask')
|
|
return typeof widget?.serializeValue === 'function'
|
|
})
|
|
|
|
await page.evaluate(async () => {
|
|
const graph = window.graph as TestGraphAccess | undefined
|
|
if (!graph) {
|
|
throw new Error(
|
|
'Global window.graph is absent. Ensure workflow fixture is loaded.'
|
|
)
|
|
}
|
|
|
|
const node = graph._nodes_by_id?.['1']
|
|
if (!node) {
|
|
throw new Error(
|
|
'Target node with ID "1" not found in graph._nodes_by_id.'
|
|
)
|
|
}
|
|
|
|
const widgetIndex = node.widgets?.findIndex((w) => w.name === 'mask') ?? -1
|
|
if (widgetIndex === -1) {
|
|
throw new Error('Widget "mask" not found on target node 1.')
|
|
}
|
|
|
|
const widget = node.widgets?.[widgetIndex]
|
|
if (!widget) {
|
|
throw new Error(`Widget index ${widgetIndex} not found on target node 1.`)
|
|
}
|
|
|
|
if (typeof widget.serializeValue !== 'function') {
|
|
throw new Error(
|
|
'mask widget on node 1 does not have a serializeValue function.'
|
|
)
|
|
}
|
|
|
|
await widget.serializeValue(node, widgetIndex)
|
|
})
|
|
}
|