Files
ComfyUI_frontend/browser_tests/fixtures/helpers/MaskEditorHelper.ts
Dante e16a0bfe82 fix(knip): narrow Playwright entrypoints so browser-test dead exports are reported (FE-717) (#12496)
## Summary

Narrow Knip's Playwright `entry` to actual spec files so dead exports in
browser-test fixtures are reported instead of being hidden by treating
every helper as an entrypoint.

## Changes

- **What**:
- `knip.config.ts`: Playwright `entry` changed from the broad
`['**/*.@(spec|test)…', 'browser_tests/**/*.ts']` to
`['browser_tests/**/*.@(spec|test).?(c|m)[jt]s?(x)']`.
`globalSetup`/`globalTeardown` stay covered via Knip's playwright config
resolution; fixtures remain in the project graph so their unused exports
surface.
- Resolved the 54 dead findings this exposed: over-exported symbols used
only within their own module are now module-private (dropped `export`,
no behavioral change); genuinely unreferenced fixtures were deleted
(asset/template `ALL_*` aggregators + orphaned `STABLE_*` data,
`TemplateHelper` distribution helpers + `generateTemplates`, dead
types/utils, and the unused `nodeDefinitions.ts` module).
- **Breaking**: none — test-only changes.

## Review Focus

- Deletions are limited to fixtures with zero importers on `main`
(verified via `pnpm knip`); the bulk of the diff is `export`-keyword
removal.
- Verified: `pnpm knip` (browser_tests clean), `pnpm typecheck`, `pnpm
typecheck:browser`, oxfmt/oxlint/eslint all pass.

Linear: FE-717
2026-06-01 07:53:29 +00:00

137 lines
4.1 KiB
TypeScript

import type { Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import { comfyPageFixture } from '@e2e/fixtures/ComfyPage'
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
const MASK_CANVAS_INDEX = 2
const RGB_CANVAS_INDEX = 1
type BrushSliderLabel = 'thickness'
class MaskEditorHelper {
constructor(private comfyPage: ComfyPage) {}
private get page() {
return this.comfyPage.page
}
async loadImageOnNode() {
await this.comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
const loadImageNode = (
await this.comfyPage.nodeOps.getNodeRefsByType('LoadImage')
)[0]
const { x, y } = await loadImageNode.getPosition()
await this.comfyPage.dragDrop.dragAndDropFile('image64x64.webp', {
dropPosition: { x, y }
})
const imagePreview = this.page.locator('.image-preview')
await expect(imagePreview).toBeVisible()
await expect(imagePreview.locator('img')).toBeVisible()
await expect(imagePreview).toContainText('x')
return {
imagePreview,
nodeId: String(loadImageNode.id)
}
}
async openDialog(): Promise<Locator> {
const { imagePreview } = await this.loadImageOnNode()
await imagePreview.getByRole('region').hover()
await this.page.getByLabel('Edit or mask image').click()
const dialog = this.page.locator('.mask-editor-dialog')
await expect(dialog).toBeVisible()
await expect(
dialog.getByRole('heading', { name: 'Mask Editor' })
).toBeVisible()
const canvasContainer = dialog.locator('#maskEditorCanvasContainer')
await expect(canvasContainer).toBeVisible()
await expect(canvasContainer.locator('canvas')).toHaveCount(4)
return dialog
}
async drawStrokeOnPointerZone(dialog: Locator) {
const pointerZone = dialog.getByTestId('pointer-zone')
await expect(pointerZone).toBeVisible()
const box = await pointerZone.boundingBox()
if (!box) throw new Error('Pointer zone bounding box not found')
const startX = box.x + box.width * 0.3
const startY = box.y + box.height * 0.5
const endX = box.x + box.width * 0.7
const endY = box.y + box.height * 0.5
await this.page.mouse.move(startX, startY)
await this.page.mouse.down()
await this.page.mouse.move(endX, endY, { steps: 10 })
await this.page.mouse.up()
return { startX, startY, endX, endY, box }
}
async drawStrokeAndExpectPixels(dialog: Locator) {
await this.drawStrokeOnPointerZone(dialog)
await expect.poll(() => this.pollMaskPixelCount()).toBeGreaterThan(0)
}
getCanvasPixelData(canvasIndex: number) {
return this.page.evaluate((idx) => {
const canvases = document.querySelectorAll(
'#maskEditorCanvasContainer canvas'
)
const canvas = canvases[idx] as HTMLCanvasElement | undefined
if (!canvas) return null
const ctx = canvas.getContext('2d')
if (!ctx) return null
const data = ctx.getImageData(0, 0, canvas.width, canvas.height)
let nonTransparentPixels = 0
for (let i = 3; i < data.data.length; i += 4) {
if (data.data[i] > 0) nonTransparentPixels++
}
return { nonTransparentPixels, totalPixels: data.data.length / 4 }
}, canvasIndex)
}
pollMaskPixelCount(): Promise<number> {
return this.getCanvasPixelData(MASK_CANVAS_INDEX).then(
(d) => d?.nonTransparentPixels ?? 0
)
}
pollRgbPixelCount(): Promise<number> {
return this.getCanvasPixelData(RGB_CANVAS_INDEX).then(
(d) => d?.nonTransparentPixels ?? 0
)
}
getCanvasSnapshot(canvasIndex: number): Promise<string> {
return this.page.evaluate((idx) => {
const canvas = document.querySelectorAll(
'#maskEditorCanvasContainer canvas'
)[idx] as HTMLCanvasElement | undefined
return canvas?.toDataURL() ?? ''
}, canvasIndex)
}
brushInput(dialog: Locator, label: BrushSliderLabel): Locator {
return dialog.getByTestId(`brush-${label}-input`)
}
}
export const maskEditorTest = comfyPageFixture.extend<{
maskEditor: MaskEditorHelper
}>({
maskEditor: async ({ comfyPage }, use) => {
await use(new MaskEditorHelper(comfyPage))
}
})