Comfy-Org / Comfy-Org/ComfyUI_frontend

test: audit screenshot tests — 107 candidates for conversion to behavioral assertions

Open
#10,667 0 comments 0 reactions 0 assignees View on GitHub
area:testing developer experience
Dominant language
TypeScript
Stars
2k
Forks
702
Avg merge
1d 8h
Merged PRs (30d)
512

Description

# Screenshot Test Audit

## Summary
- Total screenshot assertions: 204
- Keep: 97 (visual regression — layout, theme, rendering correctness)
- Convert: 107 (behavioral — can use locator checks, node counts, position assertions, property checks)

---

## Convert Candidates

| File | Line | Current Assertion | Suggested Replacement |
|------|------|-------------------|----------------------|
| `interaction.spec.ts` | 25 | `toHaveScreenshot('selected-all.png')` | `expect(await comfyPage.nodeOps.getSelectedGraphNodesCount()).toBe(totalCount)` |
| `interaction.spec.ts` | 27 | `toHaveScreenshot('deleted-all.png')` | `expect(await comfyPage.nodeOps.getGraphNodesCount()).toBe(0)` |
| `interaction.spec.ts` | 35 | `toHaveScreenshot('pinned-all.png')` | Assert `is_pinned` property on all nodes via `page.evaluate` |
| `interaction.spec.ts` | 38 | `toHaveScreenshot('unpinned-all.png')` | Assert `is_pinned === false` on all nodes via `page.evaluate` |
| `interaction.spec.ts` | 74 | `toHaveScreenshot('default.png')` | `expect(await comfyPage.nodeOps.getSelectedGraphNodesCount()).toBe(0)` |
| `interaction.spec.ts` | 79 | `toHaveScreenshot('selected-node1.png')` | `expect(await comfyPage.nodeOps.getSelectedGraphNodesCount()).toBe(1)` + verify selected node id |
| `interaction.spec.ts` | 84 | `toHaveScreenshot('selected-node2.png')` | `expect(await comfyPage.nodeOps.getSelectedGraphNodesCount()).toBe(1)` + verify different node id |
| `interaction.spec.ts` | 177 | `toHaveScreenshot('dragged-node1.png')` | Assert node position changed from original via `getPosition()` |
| `interaction.spec.ts` | 198 | `toHaveScreenshot('disconnected-edge.png')` | Assert link count on the input slot is 0 |
| `interaction.spec.ts` | 203 | `toHaveScreenshot('default.png')` | Assert link count on the input slot is restored to 1 |
| `interaction.spec.ts` | 214 | `toHaveScreenshot('disconnected-edge.png')` | Assert link count on the input slot is 0 |
| `interaction.spec.ts` | 219 | `toHaveScreenshot('moved-link.png')` | Assert link origin/target changed via `getInputLinkDetails` |
| `interaction.spec.ts` | 230 | `toHaveScreenshot('disconnected-edge.png')` | Assert link count on the input slot is 0 |
| `interaction.spec.ts` | 237 | `toHaveScreenshot('copied-link.png')` | Assert both input slots have link count 1 |
| `interaction.spec.ts` | 258 | `toHaveScreenshot('adjusted-widget-value.png')` | Assert widget value changed via `page.evaluate` |
| `interaction.spec.ts` | 286 | `toHaveScreenshot('batch_move_links.png')` | Assert initial link topology via `page.evaluate` |
| `interaction.spec.ts` | 301 | `toHaveScreenshot('batch_move_links_moved.png')` | Assert link topology changed (origin_id moved) via `page.evaluate` |
| `interaction.spec.ts` | 320 | `toHaveScreenshot('batch-disconnect-links-disconnected.png')` | Assert link count is 0 on the CLIP output slot |
| `interaction.spec.ts` | 330 | `toHaveScreenshot('default.png')` | Already tested elsewhere; remove duplicate |
| `interaction.spec.ts` | 335 | `toHaveScreenshot('text-encode-toggled-off.png')` | Assert collapsed state: `node.flags.collapsed === true` via `page.evaluate` |
| `interaction.spec.ts` | 343 | `toHaveScreenshot('text-encode-toggled-back-open.png')` | Assert `node.flags.collapsed === false` via `page.evaluate` |
| `interaction.spec.ts` | 408 | `toHaveScreenshot('node-title-edited.png')` | Assert `node.title === 'Hello World'` via `page.evaluate` |
| `interaction.spec.ts` | 442 | `toHaveScreenshot('group-selected-nodes.png')` | Assert group count increased and group contains selected nodes |
| `interaction.spec.ts` | 457 | `toHaveScreenshot('group-fit-to-contents.png')` | Assert group bounding box fits node positions |
| `interaction.spec.ts` | 469 | `toHaveScreenshot('nodes-pinned.png')` | Assert `is_pinned === true` on selected nodes |
| `interaction.spec.ts` | 474 | `toHaveScreenshot('nodes-unpinned.png')` | Assert `is_pinned === false` on selected nodes |
| `interaction.spec.ts` | 484 | `toHaveScreenshot('nodes-bypassed.png')` | Assert `node.mode === 4` (bypassed) via `page.evaluate` |
| `interaction.spec.ts` | 487 | `toHaveScreenshot('nodes-unbypassed.png')` | Assert `node.mode === 0` (active) via `page.evaluate` |
| `interaction.spec.ts` | 504 | `toHaveScreenshot('group-title-edited.png')` | Assert `group.title === 'Hello World'` via `page.evaluate` |
| `interaction.spec.ts` | 713 | `toHaveScreenshot('string_node_id.png')` | Assert node count and node types loaded correctly |
| `interaction.spec.ts` | 720 | `toHaveScreenshot('string_input.png')` | Assert node loaded with correct input configuration |
| `interaction.spec.ts` | 727 | `toHaveScreenshot('single_ksampler.png')` | Assert single KSampler node exists |
| `interaction.spec.ts` | 729 | `toHaveScreenshot('single_ksampler.png')` | Duplicate of above — remove |
| `interaction.spec.ts` | 739 | `toHaveScreenshot('fit-view-nodes.png')` | Assert canvas offset/scale indicate nodes are in view via `page.evaluate` |
| `interaction.spec.ts` | 762 | `toHaveScreenshot('selection-on-node-area.png')` | Assert selected node count matches expected |
| `interaction.spec.ts` | 838 | `toHaveScreenshot('single_ksampler_fit.png')` | Assert canvas scale/offset show the node centered |
| `interaction.spec.ts` | 949 | `toHaveScreenshot('legacy-left-drag-pan.png')` | Assert canvas offset changed via `page.evaluate` |
| `interaction.spec.ts` | 960 | `toHaveScreenshot('legacy-right-drag-select.png')` | Assert selected node count via `getSelectedGraphNodesCount()` |
| `interaction.spec.ts` | 987 | `toHaveScreenshot('legacy-click-node-select.png')` | Already asserts `selectedCount === 1`; remove screenshot |
| `interaction.spec.ts` | 1023 | `toHaveScreenshot('standard-left-drag-select.png')` | Already asserts `selectedCount === clipNodes.length`; remove screenshot |
| `interaction.spec.ts` | 1034 | `toHaveScreenshot('standard-middle-drag-pan.png')` | Assert canvas offset changed via `page.evaluate` |
| `interaction.spec.ts` | 1067 | `toHaveScreenshot('standard-click-node-select.png')` | Already asserts `selectedCount === 1`; remove screenshot |
| `interaction.spec.ts` | 1083 | `toHaveScreenshot('standard-space-drag-pan.png')` | Assert canvas offset changed via `page.evaluate` |
| `interaction.spec.ts` | 1146 | `toHaveScreenshot('standard-initial.png')` | Assert initial canvas offset/scale |
| `interaction.spec.ts` | 1154 | `toHaveScreenshot('standard-shift-wheel-pan-right.png')` | Assert canvas offset.x decreased (panned right) |
| `interaction.spec.ts` | 1162 | `toHaveScreenshot('standard-shift-wheel-pan-left.png')` | Assert canvas offset.x increased (panned left) |
| `interaction.spec.ts` | 1170 | `toHaveScreenshot('standard-shift-wheel-pan-center.png')` | Assert canvas offset.x returned to center |
| `interaction.spec.ts` | 1193 | `toHaveScreenshot('legacy-alt-shift-drag.png')` | Assert canvas offset changed or selection count |
| `interaction.spec.ts` | 646 | `toHaveScreenshot('dragging-link1.png')` | Assert link dragging state via `page.evaluate` (linking_node not null) |
| `interaction.spec.ts` | 657 | `toHaveScreenshot('dragging-link2.png')` | Assert link dragging state restored after space released |
| `interaction.spec.ts` | 664 | `toHaveScreenshot('panned-step-one.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 666 | `toHaveScreenshot('panned-step-two.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 668 | `toHaveScreenshot('panned-far-away.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 670 | `toHaveScreenshot('panned-back-from-far.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 672 | `toHaveScreenshot('panned-back-to-two.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 674 | `toHaveScreenshot('panned-back-to-one.png')` | Assert canvas offset via `page.evaluate` |
| `interaction.spec.ts` | 680 | `toHaveScreenshot('panned-touch.png')` | Assert canvas offset via `page.evaluate` |
| `copyPaste.spec.ts` | 19 | `toHaveScreenshot('copied-node.png')` | Assert node count increased by 1 |
| `copyPaste.spec.ts` | 30 | `toHaveScreenshot('copied-node-with-link.png')` | Assert node count increased and link exists on pasted node |
| `copyPaste.spec.ts` | 64 | `toHaveScreenshot('copied-widget-value.png')` | Assert widget value via `page.evaluate` equals expected (512) |
| `copyPaste.spec.ts` | 86 | `toHaveScreenshot('paste-in-text-area-with-node-previously-copied.png')` | Assert text box value via `toHaveValue()` |
| `copyPaste.spec.ts` | 100 | `toHaveScreenshot('no-node-copied.png')` | Assert node count unchanged |
| `copyPaste.spec.ts` | 111 | `toHaveScreenshot('drag-copy-copied-node.png')` | Assert node count increased by 1 |
| `nodeSearchBox.spec.ts` | 66 | `toHaveScreenshot('added-node.png')` | Assert node of type KSampler exists via `getNodeRefsByType` |
| `nodeSearchBox.spec.ts` | 75 | `toHaveScreenshot('auto-linked-node.png')` | Assert new node added and link exists |
| `nodeSearchBox.spec.ts` | 100 | `toHaveScreenshot('auto-linked-node-batch.png')` | Assert new node added with batch links |
| `nodeSearchBox.spec.ts` | 116 | `toHaveScreenshot('added-node-no-connection.png')` | Assert KSampler node added, no links to prior context |
| `nodeSearchBox.spec.ts` | 315 | `toHaveScreenshot('link-release-context-menu.png')` | Assert context menu is visible via locator |
| `nodeSearchBox.spec.ts` | 330 | `toHaveScreenshot('link-context-menu-search.png')` | Assert new node added and linked |
| `groupNode.spec.ts` | 111 | `toHaveScreenshot('group-node-copy-added-from-search.png')` | Assert group node exists on canvas (test is skipped anyway) |
| `recordAudio.spec.ts` | 28 | `toHaveScreenshot('record_audio_node.png')` | Already asserts `recordAudioNodes.length === 1`; remove screenshot |
| `primitiveNode.spec.ts` | 28 | `toHaveScreenshot('primitive_node_connected.png')` | Assert link exists between primitive and ksampler via `page.evaluate` |
| `primitiveNode.spec.ts` | 42 | `toHaveScreenshot('primitive_node_connected_dom_widget.png')` | Assert link exists between primitive and clip encoder |
| `primitiveNode.spec.ts` | 56 | `toHaveScreenshot('static_primitive_connected.png')` | Assert link exists between static primitive and ksampler |
| `graphCanvasMenu.spec.ts` | 28 | `toHaveScreenshot('canvas-with-hidden-links.png')` | Already asserts setting equals `HIDDEN_LINK`; verify via `Comfy.LinkRenderMode` |
| `graphCanvasMenu.spec.ts` | 40 | `toHaveScreenshot('canvas-with-visible-links.png')` | Already asserts setting not `HIDDEN_LINK`; remove screenshot |
| `domWidget.spec.ts` | 39 | `toHaveScreenshot('focus-mode-on.png')` | Assert focus mode CSS class or state via `page.evaluate` |
| `rightClickMenu.spec.ts` | 17 | `toHaveScreenshot('right-click-menu.png')` | Assert context menu locator `.litecontextmenu` is visible |
| `rightClickMenu.spec.ts` | 24 | `toHaveScreenshot('add-node-node-added.png')` | Assert `getGraphNodesCount()` increased by 1 + node type is `VAELoader` |
| `rightClickMenu.spec.ts` | 29 | `toHaveScreenshot('right-click-menu.png')` | Duplicate — assert `.litecontextmenu` is visible |
| `rightClickMenu.spec.ts` | 32 | `toHaveScreenshot('add-group-group-added.png')` | Assert group count increased via `page.evaluate` |
| `rightClickMenu.spec.ts` | 39 | `toHaveScreenshot('selected-2-nodes.png')` | Assert `getSelectedGraphNodesCount() === 2` |
| `rightClickMenu.spec.ts` | 49 | `toHaveScreenshot('right-click-node-group-node.png')` | Assert group node exists |
| `rightClickMenu.spec.ts` | 64 | `toHaveScreenshot('right-click-node.png')` | Assert context menu locator visible |
| `rightClickMenu.spec.ts` | 67 | `toHaveScreenshot('right-click-node-properties-panel.png')` | Assert properties panel locator visible |
| `rightClickMenu.spec.ts` | 79 | `toHaveScreenshot('right-click-node.png')` | Duplicate — assert context menu visible |
| `rightClickMenu.spec.ts` | 82 | `toHaveScreenshot('right-click-node-collapsed.png')` | Assert `node.flags.collapsed === true` via `page.evaluate` |
| `rightClickMenu.spec.ts` | 105 | `toHaveScreenshot('right-click-node-collapsed-badge.png')` | Assert node collapsed + badge mode settings |
| `rightClickMenu.spec.ts` | 117 | `toHaveScreenshot('right-click-node.png')` | Duplicate — assert context menu visible |
| `rightClickMenu.spec.ts` | 120 | `toHaveScreenshot('right-click-node-bypassed.png')` | Assert `node.mode === 4` via `page.evaluate` |
| `rightClickMenu.spec.ts` | 132 | `toHaveScreenshot('right-click-node.png')` | Duplicate — assert context menu visible |
| `rightClickMenu.spec.ts` | 140 | `toHaveScreenshot('node-pinned.png')` | Assert `node.is_pinned === true` + position unchanged after drag |
| `rightClickMenu.spec.ts` | 147 | `toHaveScreenshot('right-click-pinned-node.png')` | Assert "Unpin" menu item visible in context menu |
| `rightClickMenu.spec.ts` | 158 | `toHaveScreenshot('right-click-unpinned-node.png')` | Assert "Pin" menu item visible in context menu |
| `rightClickMenu.spec.ts` | 185 | `toHaveScreenshot('right-click-unpinned-node-moved.png')` | Assert node position changed after unpin+drag |
| `rightClickMenu.spec.ts` | 202 | `toHaveScreenshot('selected-nodes-pinned.png')` | Assert `is_pinned === true` on all selected nodes |
| `rightClickMenu.spec.ts` | 211 | `toHaveScreenshot('selected-nodes-unpinned.png')` | Assert `is_pinned === false` on all selected nodes |
| `widget.spec.ts` | 53 | `toHaveScreenshot('resized-to-original.png')` | Assert node size matches original via `page.evaluate` |
| `widget.spec.ts` | 112 | `toHaveScreenshot('boolean_widget.png')` | Assert boolean widget value is initial state via `page.evaluate` |
| `widget.spec.ts` | 116 | `toHaveScreenshot('boolean_widget_toggled.png')` | Assert boolean widget value toggled via `page.evaluate` |
| `widget.spec.ts` | 135 | `toHaveScreenshot('slider_widget_dragged.png')` | Already asserts `window.widgetValue` is defined; remove screenshot |
| `widget.spec.ts` | 156 | `toHaveScreenshot('seed_widget_dragged.png')` | Already asserts `window.widgetValue` is defined; remove screenshot |
| `widget.spec.ts` | 178 | `toHaveScreenshot('ksampler_widget_added.png')` | Assert widget count increased via `page.evaluate` |
| `widget.spec.ts` | 188 | `toHaveScreenshot('load_image_widget.png')` | Assert image widget DOM element visible via locator |
| `widget.spec.ts` | 205 | `toHaveScreenshot('image_preview_drag_and_dropped.png')` | Already asserts `filename === 'image32x32.webp'`; assert img element src changed |
| `widget.spec.ts` | 236 | `toHaveScreenshot('image_preview_changed_by_combo_value.png')` | Already asserts `filename === 'image32x32.webp'`; remove screenshot |
| `widget.spec.ts` | 375 | `toHaveScreenshot('load_audio_widget.png')` | Assert `.comfy-audio` element is attached (already done); remove screenshot |
| `selectionToolbox.spec.ts` | 44 | `toHaveScreenshot('selection-toolbox-multiple-nodes-border.png')` | Assert selection toolbox is visible (already done at line 42) |
| `selectionToolbox.spec.ts` | 92 | `toHaveScreenshot('selection-toolbox-single-node-no-border.png')` | Assert toolbox visible + single node selected |
| `selectionToolbox.spec.ts` | 103 | `toHaveScreenshot('selection-toolbox-multiple-selections-border.png')` | Assert toolbox visible + multiple nodes selected |
| `selectionToolbox.spec.ts` | 111 | `toHaveScreenshot('selection-toolbox-single-selection-no-border.png')` | Assert toolbox visible + single node selected |
| `rerouteNode.spec.ts` | 37 | `toHaveScreenshot('reroute_inserted.png')` | Assert reroute node exists and is linked via `page.evaluate` |
| `rerouteNode.spec.ts` | 52 | `toHaveScreenshot('native_reroute.png')` | Assert reroute node loaded correctly via `page.evaluate` |
| `rerouteNode.spec.ts` | 76 | `toHaveScreenshot('native_reroute_alt_click.png')` | Assert reroute node count increased |
| `rerouteNode.spec.ts` | 103 | `toHaveScreenshot('native_reroute_context_menu.png')` | Assert reroute node count increased |
| `rerouteNode.spec.ts` | 128 | `toHaveScreenshot('native_reroute_delete_from_midpoint_context_menu.png')` | Assert link count decreased after delete |
| `execution.spec.ts` | 33 | `toHaveScreenshot('execution-error-unconnected-slot.png')` | Assert error overlay dismissed + node error state via `page.evaluate` |
| `vueNodes/interactions/node/move.spec.ts` | 46 | `toHaveScreenshot('vue-node-moved-node.png')` | Already asserts position changed via `expectPosChanged`; remove screenshot |
| `vueNodes/interactions/node/move.spec.ts` | 70 | `toHaveScreenshot('vue-node-moved-node-touch.png')` | Already asserts position changed via `expectPosChanged`; remove screenshot |

---

## Keep (with justification)

| File | Line | Assertion | Justification |
|------|------|-----------|---------------|
| `interaction.spec.ts` | 249 | `toHaveScreenshot('snapped-highlighted.png')` | Visual: snap highlighting appearance is purely visual |
| `interaction.spec.ts` | 266 | `toHaveScreenshot('snap_to_slot.png')` | Visual: link snap rendering |
| `interaction.spec.ts` | 278 | `toHaveScreenshot('snap_to_slot_linked.png')` | Visual: linked slot rendering |
| `interaction.spec.ts` | 511 | `toHaveScreenshot('zoomed-in.png')` | Visual: zoom level rendering |
| `interaction.spec.ts` | 513 | `toHaveScreenshot('zoomed-out.png')` | Visual: zoom level rendering |
| `interaction.spec.ts` | 518 | `toHaveScreenshot('zoomed-very-far-out.png')` | Visual: extreme zoom rendering correctness |
| `interaction.spec.ts` | 520 | `toHaveScreenshot('zoomed-back-in.png')` | Visual: zoom recovery rendering |
| `interaction.spec.ts` | 529 | `toHaveScreenshot('zoomed-in-ctrl-shift.png')` | Visual: ctrl+shift zoom rendering |
| `interaction.spec.ts` | 531 | `toHaveScreenshot('zoomed-out-ctrl-shift.png')` | Visual: ctrl+shift zoom rendering |
| `interaction.spec.ts` | 533 | `toHaveScreenshot('zoomed-default-ctrl-shift.png')` | Visual: zoom reset rendering |
| `interaction.spec.ts` | 545 | `toHaveScreenshot('zoomed-in-low-zoom-speed.png')` | Visual: zoom speed rendering |
| `interaction.spec.ts` | 549 | `toHaveScreenshot('zoomed-out-low-zoom-speed.png')` | Visual: zoom speed rendering |
| `interaction.spec.ts` | 560 | `toHaveScreenshot('zoomed-in-high-zoom-speed.png')` | Visual: zoom speed rendering |
| `interaction.spec.ts` | 564 | `toHaveScreenshot('zoomed-out-high-zoom-speed.png')` | Visual: zoom speed rendering |
| `interaction.spec.ts` | 572 | `toHaveScreenshot('panned.png')` | Visual: pan rendering position |
| `interaction.spec.ts` | 651 | `toHaveScreenshot('panning-when-dragging-link.png')` | Visual: canvas panning while dragging link rendering |
| `interaction.spec.ts` | 969 | `toHaveScreenshot('legacy-wheel-zoom-in.png')` | Visual: legacy zoom rendering |
| `interaction.spec.ts` | 975 | `toHaveScreenshot('legacy-wheel-zoom-out.png')` | Visual: legacy zoom rendering |
| `interaction.spec.ts` | 1045 | `toHaveScreenshot('standard-ctrl-wheel-zoom-in.png')` | Visual: standard mode zoom rendering |
| `interaction.spec.ts` | 1053 | `toHaveScreenshot('standard-ctrl-wheel-zoom-out.png')` | Visual: standard mode zoom rendering |
| `nodeDisplay.spec.ts` | 14 | `toHaveScreenshot('optional_input.png')` | Visual: optional input hollow circle shape rendering |
| `nodeDisplay.spec.ts` | 19 | `toHaveScreenshot('optional_input.png')` | Visual: optional input rendering with wrong shape |
| `nodeDisplay.spec.ts` | 24 | `toHaveScreenshot('optional_input.png')` | Visual: optional input rendering with correct shape |
| `nodeDisplay.spec.ts` | 29 | `toHaveScreenshot('force_input.png')` | Visual: force input shape rendering |
| `nodeDisplay.spec.ts` | 34 | `toHaveScreenshot('default_input.png')` | Visual: default input shape rendering |
| `nodeDisplay.spec.ts` | 73 | `toHaveScreenshot('simple_slider.png')` | Visual: slider widget rendering |
| `nodeDisplay.spec.ts` | 83 | `toHaveScreenshot('missing_nodes_converted_widget.png')` | Visual: missing node display rendering |
| `nodeDisplay.spec.ts` | 89 | `toHaveScreenshot('dynamically_added_input.png')` | Visual: dynamic input rendering |
| `nodeBadge.spec.ts` | 26 | `toHaveScreenshot('node-badge.png')` | Visual: badge rendering appearance |
| `nodeBadge.spec.ts` | 46 | `toHaveScreenshot('node-badge-multiple.png')` | Visual: multiple badges layout |
| `nodeBadge.spec.ts` | 65 | `toHaveScreenshot('node-badge-left.png')` | Visual: left-side badge positioning |
| `nodeBadge.spec.ts` | 87 | `toHaveScreenshot('node-badge-${mode}.png')` | Visual: badge mode display variants |
| `nodeBadge.spec.ts` | 110 | `toHaveScreenshot('node-badge-unknown-color-palette.png')` | Visual: badge colors on unknown palette |
| `nodeBadge.spec.ts` | 126 | `toHaveScreenshot('node-badge-light-color-palette.png')` | Visual: badge colors on light palette |
| `primitiveNode.spec.ts` | 13 | `toHaveScreenshot('primitive_node.png')` | Visual: primitive node sizing/layout |
| `noteNode.spec.ts` | 12 | `toHaveScreenshot('note_nodes.png')` | Visual: note node rendering |
| `loadWorkflowInMedia.spec.ts` | 51 | `toHaveScreenshot('${fileName}.png')` | Visual: workflow loaded from media renders correctly (multiple files) |
| `loadWorkflowInMedia.spec.ts` | 65 | `toHaveScreenshot('dropped_workflow_url_${readableName}.png')` | Visual: workflow loaded from URL renders correctly |
| `colorPalette.spec.ts` | 166 | `toHaveScreenshot('custom-color-palette-obsidian-dark-all-colors.png')` | Visual: theme color rendering |
| `colorPalette.spec.ts` | 171 | `toHaveScreenshot('custom-color-palette-light-red.png')` | Visual: theme color rendering |
| `colorPalette.spec.ts` | 177 | `toHaveScreenshot('default-color-palette.png')` | Visual: default theme rendering |
| `colorPalette.spec.ts` | 190 | `toHaveScreenshot('custom-color-palette-obsidian-dark.png')` | Visual: custom theme rendering |
| `colorPalette.spec.ts` | 199 | `toHaveScreenshot('custom-color-palette-obsidian-dark.png')` | Visual: legacy prefix theme rendering |
| `colorPalette.spec.ts` | 221 | `toHaveScreenshot('node-opacity-0.5.png')` | Visual: opacity rendering |
| `colorPalette.spec.ts` | 226 | `toHaveScreenshot('node-opacity-1.png')` | Visual: opacity rendering |
| `colorPalette.spec.ts` | 236 | `toHaveScreenshot('node-opacity-0.2-arc-theme.png')` | Visual: opacity + theme rendering |
| `colorPalette.spec.ts` | 270 | `toHaveScreenshot('node-lightened-colors.png')` | Visual: light theme color adjustment |
| `colorPalette.spec.ts` | 289 | `toHaveScreenshot('node-opacity-0.3-color-changed.png')` | Visual: opacity + custom color rendering |
| `colorPalette.spec.ts` | 300 | `toHaveScreenshot('node-opacity-0.3-color-removed.png')` | Visual: opacity after color removal |
| `saveImageAndWebp.spec.ts` | 37 | `toHaveScreenshot('save-image-and-webm-preview.png')` | Visual: image/video preview rendering layout |
| `widget.spec.ts` | 18 | `toHaveScreenshot('load-checkpoint-resized-min-width.png')` | Visual: widget text truncation rendering |
| `widget.spec.ts` | 28 | `toHaveScreenshot('ksampler-resized-min-width.png')` | Visual: widget text truncation rendering |
| `widget.spec.ts` | 40 | `toHaveScreenshot('empty-latent-resized-80-percent.png')` | Visual: widget text not truncated rendering |
| `widget.spec.ts` | 265 | `toHaveScreenshot('image_preview_close_button.png')` | Visual: close button rendering on image preview |
| `widget.spec.ts` | 295 | `toHaveScreenshot('animated_image_preview_drag_and_dropped.png')` | Visual: animated image preview rendering (test skipped) |
| `widget.spec.ts` | 303 | `toHaveScreenshot('animated_image_preview_drag_and_dropped_next_frame.png')` | Visual: animated image next frame rendering (test skipped) |
| `mobileBaseline.spec.ts` | 17 | `toHaveScreenshot('mobile-empty-canvas.png')` | Visual: mobile layout rendering |
| `mobileBaseline.spec.ts` | 22 | `toHaveScreenshot('mobile-default-workflow.png')` | Visual: mobile workflow layout |
| `mobileBaseline.spec.ts` | 31 | `toHaveScreenshot('mobile-settings-dialog.png')` | Visual: mobile settings dialog layout |
| `lodThreshold.spec.ts` | 173 | `toHaveScreenshot('lod-comparison-low-quality.png')` | Visual: LOD low-quality rendering |
| `lodThreshold.spec.ts` | 194 | `toHaveScreenshot('lod-comparison-high-quality.png')` | Visual: LOD high-quality rendering comparison |
| `templates.spec.ts` | 318 | `toHaveScreenshot('template-grid-varying-content.png')` | Visual: template grid layout consistency |
| `viewport.spec.ts` | 18 | `toHaveScreenshot('viewport-fits-when-saved-offscreen.png')` | Visual: viewport fit behavior rendering |
| `vueNodes/groups/groups.spec.ts` | 20 | `toHaveScreenshot('vue-groups-create-group.png')` | Visual: Vue group rendering |
| `vueNodes/groups/groups.spec.ts` | 31 | `toHaveScreenshot('vue-groups-fit-to-contents.png')` | Visual: Vue group fit rendering |
| `vueNodes/nodeStates/colors.spec.ts` | 35 | `toHaveScreenshot('vue-node-custom-color-blue.png')` | Visual: Vue node color rendering |
| `vueNodes/nodeStates/colors.spec.ts` | 42 | `toHaveScreenshot('vue-node-custom-colors-dark-all-colors.png')` | Visual: Vue node all colors dark theme |
| `vueNodes/nodeStates/colors.spec.ts` | 52 | `toHaveScreenshot('vue-node-custom-colors-light-all-colors.png')` | Visual: Vue node all colors light theme |
| `vueNodes/nodeStates/mute.spec.ts` | 25 | `toHaveScreenshot('vue-node-muted-state.png')` | Visual: muted node rendering appearance |
| `vueNodes/nodeStates/bypass.spec.ts` | 31 | `toHaveScreenshot('vue-node-bypassed-state.png')` | Visual: bypassed node rendering appearance |
| `vueNodes/interactions/node/imagePreview.spec.ts` | 128 | `toHaveScreenshot('vue-node-multiple-promoted-previews.png')` | Visual: multiple image preview layout |
| `vueNodes/interactions/node/bringToFront.spec.ts` | 71 | `toHaveScreenshot('bring-to-front-overlapped-before.png')` | Visual: z-index overlap rendering |
| `vueNodes/interactions/node/bringToFront.spec.ts` | 96 | `toHaveScreenshot('bring-to-front-overlapped-after.png')` | Visual: z-index after click rendering |
| `vueNodes/interactions/node/bringToFront.spec.ts` | 123 | `toHaveScreenshot('bring-to-front-widget-overlapped-before.png')` | Visual: widget overlap rendering |
| `vueNodes/interactions/node/bringToFront.spec.ts` | 140 | `toHaveScreenshot('bring-to-front-widget-overlapped-after.png')` | Visual: widget overlap after click |
| `vueNodes/interactions/canvas/zoom.spec.ts` | 34 | `toHaveScreenshot('zoomed-in-ctrl-shift.png')` | Visual: Vue nodes zoom rendering |
| `vueNodes/interactions/canvas/pan.spec.ts` | 20 | `toHaveScreenshot('vue-nodes-paned-with-touch.png')` | Visual: Vue nodes pan rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 137 | `toHaveScreenshot('vue-node-dragging-link.png')` | Visual: link dragging rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 264 | `toHaveScreenshot('vue-node-reconnected-link.png')` | Visual: reconnected link rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 313 | `toHaveScreenshot('vue-node-batch-move.png')` | Visual: batch link move rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 481 | `toHaveScreenshot('vue-node-parallel-links.png')` | Visual: parallel links rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 573 | `toHaveScreenshot('vue-node-alt-click-link-deleted.png')` | Visual: link deletion rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 716 | `toHaveScreenshot('vue-node-snap-highlight.png')` | Visual: snap highlight rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 754 | `toHaveScreenshot('vue-node-snap-to-node.png')` | Visual: snap-to-node rendering |
| `vueNodes/interactions/links/linkInteraction.spec.ts` | 809 | `toHaveScreenshot('vue-node-snap-to-slot.png')` | Visual: snap-to-slot rendering |

---

## Notes

- **No test files were modified** — this is an audit only.
- Many "Convert" candidates already have partial behavioral assertions in the same test; the screenshot is redundant.
- The zoom/pan screenshots in `interaction.spec.ts` are borderline — kept because canvas rendering at different zoom levels catches rendering bugs not detectable via offset/scale values alone.
- All `colorPalette.spec.ts`, `lodThreshold.spec.ts`, `mobileBaseline.spec.ts`, and `nodeBadge.spec.ts` screenshots are "Keep" because visual correctness IS the behavior under test.
- Vue node link rendering screenshots are kept because link spline paths, colors, and z-ordering are purely visual concerns.

┆Issue is synchronized with this [Notion page](https://www.notion.so/Issue-10667-test-audit-screenshot-tests-107-candidates-for-conversion-to-behavioral-assertions-3316d73d365081cb9062e4ae93b8376d) by [Unito](https://www.unito.io)

Contributor guide

Open the contributing guide

Research direction

Start with the listed interaction.spec.ts candidates and compare their existing screenshot assertions with the suggested behavioral checks. Run the affected Playwright tests first, then work through the named spec files, preserving screenshots for visual-regression cases and replacing only behavioral duplicates with assertions that verify the stated outcomes.

Written by the indexing model from the issue text.

Assessment

Tech stack
playwright, typescript
Domain
testing-qa
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.