Compare commits

...

11 Commits

Author SHA1 Message Date
Alexander Brown
b4e3b7191a Merge branch 'main' into glary/test-mask-editor-load-save 2026-05-18 15:34:41 -07:00
Glary-Bot
97a5a85817 fix: align mask editor load/save tests with saver upload ordering
The 'Opening mask editor loads...' test polled the RGB (paint) canvas
for pixels, but on dialog open the loaded image lives on the image
canvas; the paint canvas is empty until the user draws. The assertion
was also redundant with the 'Canvas dimensions match the loaded image'
test that verifies the image canvas is sized to the source image.

The 'Save failure on partial upload' test mocked /upload/mask to fail
first, but useMaskEditorSaver uploads layers sequentially (mask first,
then image), so the failure short-circuited before /upload/image was
hit and the imageUploadHit assertion always timed out. Swap the routes
so mask succeeds and image fails: both endpoints get exercised, the
'partial' wording in the test name is honored, and the dialog-stays-
visible behavior is verified the same way.
2026-05-13 23:43:37 +00:00
Alexander Brown
0eac644532 Merge branch 'main' into glary/test-mask-editor-load-save 2026-05-13 15:40:09 -07:00
Alexander Brown
44513a0723 Merge branch 'main' into glary/test-mask-editor-load-save 2026-05-06 17:29:48 -07:00
Glary-Bot
2b83f85681 test: refactor mask editor load/save spec to use MaskEditorHelper fixture
Address christian-byrne's review feedback:

1. Helper duplication: switch to the existing MaskEditorHelper fixture
   (which now lives in browser_tests/fixtures/helpers/MaskEditorHelper.ts
   and is consumed by maskEditor.spec.ts), removing all locally-defined
   loadImageOnNode/openMaskEditorDialog/drawStroke/getCanvasPixelData
   helpers from this spec.

2. Brittle pointer-zone selector: the helper now uses getByTestId('pointer-zone')
   targeting the data-testid added in PointerZone.vue, replacing the previous
   Tailwind class-fragment selector.

3. Save-and-reopen test removed: the prior assertion was unreliable because
   /view (used by useMaskEditorLoader to re-fetch saved layers) was not
   mocked, so a non-zero pixel count after reopen could come from the
   original image rather than from the freshly-saved mask. Mocking /view
   with a generated image would make the assertion circular. The
   useMaskEditorSaver round-trip is already covered by unit tests in
   src/composables/maskeditor/useMaskEditorSaver.test.ts.
2026-05-05 19:36:38 +00:00
Alexander Brown
d8de9d2366 Merge branch 'main' into glary/test-mask-editor-load-save 2026-05-05 12:30:44 -07:00
bymyself
6b64f80245 fix: type route.fulfill() mock payloads with UploadResponse interface
Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11369#pullrequestreview-2900820990
2026-04-20 02:34:06 -07:00
Glary-Bot
5fdb8b3875 fix: add LoadImage node guard and assert both upload endpoints in failure test 2026-04-18 20:41:39 +00:00
Glary-Bot
a684b62b17 fix: verify upload endpoints are hit in failure test 2026-04-18 20:37:31 +00:00
Glary-Bot
960ee85d2e fix: address CodeRabbit review - consolidate pixel helpers, fix test title, strengthen upload assertions 2026-04-18 20:33:00 +00:00
Glary-Bot
d39d91e98f test: add mask editor load/save round-trip browser tests 2026-04-18 20:23:46 +00:00

View File

@@ -0,0 +1,103 @@
import { expect } from '@playwright/test'
import { maskEditorTest as test } from '@e2e/fixtures/helpers/MaskEditorHelper'
interface UploadResponse {
name: string
subfolder: string
type: 'input' | 'output' | 'temp'
}
const IMAGE_CANVAS_INDEX = 0
const MASK_CANVAS_INDEX = 2
const successResponse = (name: string): UploadResponse => ({
name,
subfolder: 'clipspace',
type: 'input'
})
const fulfillJson = (body: UploadResponse) => ({
status: 200,
contentType: 'application/json',
body: JSON.stringify(body)
})
test.describe('Mask Editor load/save', { tag: '@vue-nodes' }, () => {
test('Save with drawn mask uploads non-empty mask data', async ({
comfyPage,
maskEditor
}) => {
const dialog = await maskEditor.openDialog()
await maskEditor.drawStrokeAndExpectPixels(dialog)
let observedContentType = ''
let observedBodyLength = 0
await comfyPage.page.route('**/upload/mask', async (route) => {
const request = route.request()
observedContentType = (await request.headerValue('content-type')) ?? ''
observedBodyLength = request.postDataBuffer()?.byteLength ?? 0
await route.fulfill(
fulfillJson(successResponse('clipspace-mask-123.png'))
)
})
await comfyPage.page.route('**/upload/image', (route) =>
route.fulfill(fulfillJson(successResponse('clipspace-painted-123.png')))
)
await dialog.getByRole('button', { name: 'Save' }).click()
await expect(dialog).toBeHidden()
expect(observedContentType).toContain('multipart/form-data')
expect(observedBodyLength).toBeGreaterThan(256)
})
test('Canvas dimensions match the loaded image', async ({ maskEditor }) => {
const dialog = await maskEditor.openDialog()
const imageDimensions =
await maskEditor.getCanvasPixelData(IMAGE_CANVAS_INDEX)
const maskDimensions =
await maskEditor.getCanvasPixelData(MASK_CANVAS_INDEX)
expect(imageDimensions).not.toBeNull()
expect(maskDimensions).not.toBeNull()
expect(imageDimensions?.totalPixels).toBe(64 * 64)
expect(maskDimensions?.totalPixels).toBe(64 * 64)
await expect(dialog).toBeVisible()
})
test('Save failure on partial upload keeps dialog open', async ({
comfyPage,
maskEditor
}) => {
const dialog = await maskEditor.openDialog()
await maskEditor.drawStrokeAndExpectPixels(dialog)
// The saver uploads sequentially: mask layer first, then image layers.
// Let the mask upload succeed and the image upload fail to exercise both
// endpoints and verify the dialog stays open after a partial failure.
let maskUploadHit = false
let imageUploadHit = false
await comfyPage.page.route('**/upload/mask', (route) => {
maskUploadHit = true
return route.fulfill(
fulfillJson(successResponse('clipspace-mask-999.png'))
)
})
await comfyPage.page.route('**/upload/image', (route) => {
imageUploadHit = true
return route.fulfill({ status: 500 })
})
const saveButton = dialog.getByRole('button', { name: 'Save' })
await saveButton.click()
await expect.poll(() => maskUploadHit).toBe(true)
await expect.poll(() => imageUploadHit).toBe(true)
await expect(dialog).toBeVisible()
await expect(saveButton).toBeVisible()
})
})