-
Notifications
You must be signed in to change notification settings - Fork 673
test: add ECS migration regression coverage #15327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
6001bbc
test: add ECS migration regression coverage
ampagent 4842171
test: make migration regression coverage behavior-focused
ampagent da6e1f7
test: cover mixed node drag selection
ampagent 94c5aad
Apply suggestions from code review
DrJKL 3e273c6
Update browser_tests/fixtures/helpers/CanvasHelper.ts
DrJKL 81170c5
test: prefer behavior-level ECS regression coverage
ampagent f3722ee
test: strengthen behavior-level regression assertions
ampagent 9dafbcc
test: expose semantic widget error state
ampagent 7e2945d
test: cover cyclic subgraph definition collection
ampagent 2a8e0d5
test: add more migration-safe behavior coverage
ampagent 37072a8
test: strengthen additional migration behavior coverage
ampagent f6ee63e
test: address behavior coverage review feedback
ampagent 951a9e6
Merge remote-tracking branch 'origin/main' into test/ecs-migration-re…
ampagent 55550fd
test: select reroute fixture through Vue node UI
ampagent bd1348f
test: assert touch pan behavior directly
ampagent d93152b
Merge remote-tracking branch 'origin/main' into test/ecs-migration-re…
ampagent 532214f
test: create valid floating link fixture
ampagent 36588fc
test: keep regression coverage behavioral
ampagent 44abed6
test: use pinned node matcher
ampagent d79ea90
test: reuse bounds matcher for node growth
ampagent 0019f39
Merge branch 'main' into test/ecs-migration-regression-coverage
DrJKL 5c49957
[automated] Update test expectations
invalid-email-address File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
208 changes: 208 additions & 0 deletions
208
browser_tests/tests/vueNodes/layout/rendererToggleGeometry.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import { | ||
| comfyExpect as expect, | ||
| comfyPageFixture as test | ||
| } from '@e2e/fixtures/ComfyPage' | ||
| import type { ComfyPage } from '@e2e/fixtures/ComfyPage' | ||
| import { fitToViewInstant } from '@e2e/fixtures/utils/fitToView' | ||
| import type { Point } from '@/lib/litegraph/src/litegraph' | ||
| import type { NodeId } from '@/types/nodeId' | ||
| import { toNodeId } from '@/types/nodeId' | ||
|
|
||
| const LEGACY_TITLE_HEIGHT = 30 | ||
|
|
||
| interface NodeGeometry { | ||
| pos: Point | ||
| size: Point | ||
| inputs: Point[] | ||
| outputs: Point[] | ||
| } | ||
|
|
||
| /** | ||
| * Read slots through the accessors used by `drawConnections`; raw fields | ||
| * bypass the store projection under test. | ||
| */ | ||
| async function readGeometry( | ||
| comfyPage: ComfyPage, | ||
| nodeId: NodeId | ||
| ): Promise<NodeGeometry> { | ||
| return comfyPage.page.evaluate((id): NodeGeometry => { | ||
| const node = window.app?.canvas.graph?.getNodeById(id) | ||
| if (!node) throw new Error(`Node ${id} not found`) | ||
|
|
||
| return { | ||
| pos: [node.pos[0], node.pos[1]], | ||
| size: [node.size[0], node.size[1]], | ||
| inputs: node.inputs.map((_, i) => node.getInputPos(i)), | ||
| outputs: node.outputs.map((_, i) => node.getOutputPos(i)) | ||
| } | ||
| }, nodeId) | ||
| } | ||
|
|
||
| function slotsOf(geometry: NodeGeometry): Point[] { | ||
| return [...geometry.inputs, ...geometry.outputs] | ||
| } | ||
|
|
||
| function expectSlotsTrackedNode(after: NodeGeometry, before: NodeGeometry) { | ||
| const dx = after.pos[0] - before.pos[0] | ||
| const dy = after.pos[1] - before.pos[1] | ||
| expect(Math.abs(dx) + Math.abs(dy), 'drag moved the node').toBeGreaterThan(1) | ||
|
|
||
| const beforeSlots = slotsOf(before) | ||
| const afterSlots = slotsOf(after) | ||
| expect(afterSlots, 'slot count after drag').toHaveLength(beforeSlots.length) | ||
| afterSlots.forEach(([x, y], i) => { | ||
| expect(x, `slot ${i} x tracked the node`).toBeCloseTo( | ||
| beforeSlots[i][0] + dx, | ||
| 0 | ||
| ) | ||
| expect(y, `slot ${i} y tracked the node`).toBeCloseTo( | ||
| beforeSlots[i][1] + dy, | ||
| 0 | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| function expectGeometryPreserved( | ||
| actual: NodeGeometry, | ||
| reference: NodeGeometry, | ||
| label: string | ||
| ) { | ||
| expect(actual.pos[0], `${label}: x`).toBeCloseTo(reference.pos[0], 0) | ||
| expect(actual.pos[1], `${label}: y`).toBeCloseTo(reference.pos[1], 0) | ||
| expect(actual.size, `${label}: size`).toEqual(reference.size) | ||
|
|
||
| const referenceSlots = slotsOf(reference) | ||
| const actualSlots = slotsOf(actual) | ||
| expect(actualSlots, `${label}: slot count`).toHaveLength( | ||
| referenceSlots.length | ||
| ) | ||
| actualSlots.forEach(([x, y], i) => { | ||
| expect(x, `${label}: slot ${i} x`).toBeCloseTo(referenceSlots[i][0], 0) | ||
| expect(y, `${label}: slot ${i} y`).toBeCloseTo(referenceSlots[i][1], 0) | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Renderers compute different slot offsets, so require slots only to remain | ||
| * within their node bounds. | ||
| */ | ||
| function expectSlotsOnNode(geometry: NodeGeometry, label: string) { | ||
| const MARGIN = 20 | ||
| const [x, y] = geometry.pos | ||
| const [width, height] = geometry.size | ||
|
|
||
| slotsOf(geometry).forEach(([slotX, slotY], i) => { | ||
| expect(slotX, `${label}: slot ${i} x within node`).toBeGreaterThanOrEqual( | ||
| x - MARGIN | ||
| ) | ||
| expect(slotX, `${label}: slot ${i} x within node`).toBeLessThanOrEqual( | ||
| x + width + MARGIN | ||
| ) | ||
| expect(slotY, `${label}: slot ${i} y within node`).toBeGreaterThanOrEqual( | ||
| y - LEGACY_TITLE_HEIGHT - MARGIN | ||
| ) | ||
| expect(slotY, `${label}: slot ${i} y within node`).toBeLessThanOrEqual( | ||
| y + height + MARGIN | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| async function setVueMode(comfyPage: ComfyPage, enabled: boolean) { | ||
| await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', enabled) | ||
| if (enabled) await comfyPage.vueNodes.waitForNodes() | ||
| await comfyPage.nextFrame() | ||
| } | ||
|
|
||
| test.describe('Renderer toggle geometry', { tag: ['@vue-nodes'] }, () => { | ||
| test('slot geometry survives a Vue to legacy round trip', async ({ | ||
| comfyPage | ||
| }) => { | ||
| const nodeId = toNodeId( | ||
| await comfyPage.vueNodes.getNodeIdByTitle('KSampler') | ||
| ) | ||
|
|
||
| const before = await readGeometry(comfyPage, nodeId) | ||
| expect( | ||
| slotsOf(before).length, | ||
| 'fixture node must have slots for this test to mean anything' | ||
| ).toBeGreaterThan(0) | ||
|
|
||
| const { header } = await comfyPage.vueNodes.getFixtureByTitle('KSampler') | ||
| const headerBox = await header.boundingBox() | ||
| if (!headerBox) throw new Error('KSampler header not found') | ||
|
|
||
| const startX = headerBox.x + headerBox.width / 2 | ||
| const startY = headerBox.y + headerBox.height / 2 | ||
| await comfyPage.page.mouse.move(startX, startY) | ||
| await comfyPage.page.mouse.down() | ||
| await comfyPage.page.mouse.move(startX + 120, startY + 90, { steps: 10 }) | ||
| await comfyPage.page.mouse.up() | ||
| await comfyPage.nextFrame() | ||
|
|
||
| const moved = await readGeometry(comfyPage, nodeId) | ||
| expectSlotsTrackedNode(moved, before) | ||
|
|
||
| await setVueMode(comfyPage, false) | ||
|
|
||
| const legacyGeometry = await readGeometry(comfyPage, nodeId) | ||
| expect(legacyGeometry.pos[0], 'legacy x').toBeCloseTo(moved.pos[0], 0) | ||
| expect(legacyGeometry.pos[1], 'legacy y').toBeCloseTo(moved.pos[1], 0) | ||
| expect(legacyGeometry.size, 'legacy size').toEqual(moved.size) | ||
| expectSlotsOnNode(legacyGeometry, 'after switching to legacy') | ||
|
|
||
| await setVueMode(comfyPage, true) | ||
|
|
||
| expectGeometryPreserved( | ||
| await readGeometry(comfyPage, nodeId), | ||
| moved, | ||
| 'after switching back to Vue' | ||
| ) | ||
| }) | ||
|
|
||
| test( | ||
| 'preserves frontmost order after a legacy drag', | ||
| { tag: ['@node'] }, | ||
| async ({ comfyPage }) => { | ||
| await comfyPage.workflow.loadWorkflow('vueNodes/simple-triple') | ||
| await setVueMode(comfyPage, false) | ||
| await fitToViewInstant(comfyPage) | ||
|
|
||
| const [ksampler] = await comfyPage.nodeOps.getNodeRefsByTitle('KSampler') | ||
| const [clip] = await comfyPage.nodeOps.getNodeRefsByTitle( | ||
| 'CLIP Text Encode (Prompt)' | ||
| ) | ||
| const ksamplerPosition = await ksampler.getPosition() | ||
| const clipPosition = await clip.getPosition() | ||
|
|
||
| await ksampler.dragBy({ | ||
| x: clipPosition.x - ksamplerPosition.x, | ||
| y: clipPosition.y - ksamplerPosition.y | ||
| }) | ||
| await comfyPage.nextFrame() | ||
|
|
||
| const lastNodeId = await comfyPage.page.evaluate( | ||
| () => window.app?.graph.nodes.at(-1)?.id | ||
| ) | ||
| expect(lastNodeId, 'KSampler is frontmost after the legacy drag').toBe( | ||
| ksampler.id | ||
| ) | ||
|
|
||
| await setVueMode(comfyPage, true) | ||
| await expect(comfyPage.vueNodes.nodes).toHaveCount(3) | ||
|
|
||
| const ksamplerNode = comfyPage.vueNodes.getNodeByTitle('KSampler') | ||
| const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode') | ||
| await expect | ||
| .poll(async () => { | ||
| const ksamplerZIndex = await ksamplerNode.evaluate((node) => | ||
| Number(getComputedStyle(node).zIndex) | ||
| ) | ||
| const clipZIndex = await clipNode.evaluate((node) => | ||
| Number(getComputedStyle(node).zIndex) | ||
| ) | ||
| return ksamplerZIndex - clipZIndex | ||
| }) | ||
| .toBeGreaterThan(0) | ||
| } | ||
| ) | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.