mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-07 22:20:03 +00:00
fix: deterministic DOM widget clip-path for flaky screenshot test (#9400)
## Summary Fix deterministic DOM widget clip-path rendering to resolve the flaky "Can drag node" screenshot test. ## Root Cause `useDomClipping.updateClipPath()` schedules clip-path calculation in a `requestAnimationFrame`, but `DomWidget.vue`'s watcher reads `clippingStyle.value` synchronously before the RAF fires. The stale clip-path gets baked into `style.value` and never updated when the RAF completes, causing the textarea DOM widget to non-deterministically render in front of or behind the canvas-drawn node selection border. ## Fix - Extract `composeStyle()` function and add a dedicated watcher on `clippingStyle` that recomposes the final inline style whenever the RAF-deferred clip-path updates - Add `enableDomClipping` to the main watcher dependency array so toggling the clipping setting immediately recomposes the style - Add `moveMouseToEmptyArea()` call in the test as a secondary stabilizer against hover highlight non-determinism - Delete stale snapshot so CI regenerates it with correct clip-path behavior - Fixes #4658 --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -171,6 +171,8 @@ test.describe('Node Interaction', () => {
|
||||
|
||||
test('Can drag node', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.nodeOps.dragTextEncodeNode2()
|
||||
// Move mouse away to avoid hover highlight on the node at the drop position.
|
||||
await comfyPage.canvasOps.moveMouseToEmptyArea()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('dragged-node1.png')
|
||||
})
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 98 KiB |
@@ -102,30 +102,40 @@ const updateDomClipping = () => {
|
||||
* and update the position of the widget accordingly.
|
||||
*/
|
||||
const { left, top } = useElementBounding(canvasStore.getCanvas().canvas)
|
||||
|
||||
function composeStyle() {
|
||||
const override = widgetState.positionOverride
|
||||
const isDisabled = override
|
||||
? (override.widget.computedDisabled ?? widget.computedDisabled)
|
||||
: widget.computedDisabled
|
||||
|
||||
style.value = {
|
||||
...positionStyle.value,
|
||||
...(enableDomClipping.value ? clippingStyle.value : {}),
|
||||
zIndex: widgetState.zIndex,
|
||||
pointerEvents: widgetState.readonly || isDisabled ? 'none' : 'auto',
|
||||
opacity: isDisabled ? 0.5 : 1
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => widgetState, left, top],
|
||||
([widgetState, _, __]) => {
|
||||
[() => widgetState, left, top, enableDomClipping],
|
||||
([widgetState]) => {
|
||||
updatePosition(widgetState)
|
||||
if (enableDomClipping.value) {
|
||||
updateDomClipping()
|
||||
}
|
||||
|
||||
const override = widgetState.positionOverride
|
||||
const isDisabled = override
|
||||
? (override.widget.computedDisabled ?? widget.computedDisabled)
|
||||
: widget.computedDisabled
|
||||
|
||||
style.value = {
|
||||
...positionStyle.value,
|
||||
...(enableDomClipping.value ? clippingStyle.value : {}),
|
||||
zIndex: widgetState.zIndex,
|
||||
pointerEvents: widgetState.readonly || isDisabled ? 'none' : 'auto',
|
||||
opacity: isDisabled ? 0.5 : 1
|
||||
}
|
||||
composeStyle()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Recompose style when clippingStyle updates asynchronously via RAF.
|
||||
// updateClipPath() schedules clip-path calculation in a requestAnimationFrame,
|
||||
// so clippingStyle.value updates after the main watcher has already composed
|
||||
// style. This watcher ensures the new clip-path is applied to the DOM.
|
||||
watch(clippingStyle, composeStyle, { deep: true })
|
||||
|
||||
watch(
|
||||
() => widgetState.visible,
|
||||
(newVisible, oldVisible) => {
|
||||
|
||||
Reference in New Issue
Block a user