|
| 1 | +import { test, expect } from '@playwright/test' |
| 2 | +import { login } from './support/auth' |
| 3 | + |
| 4 | +test('the "+" add-next button aligns consistently across node types', async ({ page }) => { |
| 5 | + await login(page) |
| 6 | + |
| 7 | + await page.goto('/workflows/workflows') |
| 8 | + await page.getByRole('button', { name: 'Add workflow' }).click() |
| 9 | + await page.waitForURL(/\/workflows\/workflows\/new$/) |
| 10 | + |
| 11 | + // Chain a trigger -> LLM -> action, matching build-workflow.spec.ts, so we get one |
| 12 | + // instance of each of the three node components that render the shared add-button. |
| 13 | + await page.getByRole('button', { name: 'Add trigger' }).click() |
| 14 | + await page.getByRole('button', { name: 'Manual Trigger', exact: true }).click() |
| 15 | + await expect(page.locator('.workflows-node-trigger')).toBeVisible() |
| 16 | + |
| 17 | + await page.locator('.workflows-node-trigger .workflows-node-add-button').click() |
| 18 | + await page.getByRole('button', { name: 'LLM Prompt', exact: true }).click() |
| 19 | + await expect(page.locator('.workflows-node-llm')).toBeVisible() |
| 20 | + |
| 21 | + await page.locator('.workflows-node-llm .workflows-node-add-button').click() |
| 22 | + await page.getByRole('button', { name: 'Add Tag', exact: true }).click() |
| 23 | + await expect(page.locator('.workflows-node-action')).toBeVisible() |
| 24 | + |
| 25 | + const centerOffsets: number[] = [] |
| 26 | + |
| 27 | + for (const cardClass of ['workflows-node-trigger', 'workflows-node-llm', 'workflows-node-action']) { |
| 28 | + const card = page.locator(`.${cardClass}`) |
| 29 | + const cardBox = await card.boundingBox() |
| 30 | + const buttonBox = await card.locator('.workflows-node-add-button').boundingBox() |
| 31 | + expect(cardBox, `${cardClass} card should have a bounding box`).not.toBeNull() |
| 32 | + expect(buttonBox, `${cardClass} add-button should have a bounding box`).not.toBeNull() |
| 33 | + if (!cardBox || !buttonBox) continue |
| 34 | + |
| 35 | + // The button must be vertically centered on its own card, regardless of the |
| 36 | + // card's height or its border-radius shape (e.g. the trigger's pill-shaped left |
| 37 | + // corners must not throw off the button sitting on the right edge). |
| 38 | + const cardCenterY = cardBox.y + cardBox.height / 2 |
| 39 | + const buttonCenterY = buttonBox.y + buttonBox.height / 2 |
| 40 | + expect( |
| 41 | + Math.abs(buttonCenterY - cardCenterY), |
| 42 | + `${cardClass} add-button should be vertically centered on the card` |
| 43 | + ).toBeLessThanOrEqual(1) |
| 44 | + |
| 45 | + // The button must sit essentially at the card's right edge -- coordinated with Vue |
| 46 | + // Flow's own connection handle there -- rather than floating away from the card |
| 47 | + // with a visible, disconnected-looking gap. |
| 48 | + const buttonCenterX = buttonBox.x + buttonBox.width / 2 |
| 49 | + const cardRightX = cardBox.x + cardBox.width |
| 50 | + const offsetFromEdge = buttonCenterX - cardRightX |
| 51 | + expect( |
| 52 | + Math.abs(offsetFromEdge), |
| 53 | + `${cardClass} add-button should sit right at the card's edge, not floating away from it` |
| 54 | + ).toBeLessThanOrEqual(6) |
| 55 | + |
| 56 | + centerOffsets.push(offsetFromEdge) |
| 57 | + } |
| 58 | + |
| 59 | + // And that offset must be identical (within a tight tolerance) across all three node |
| 60 | + // types, so the button can't silently drift out of sync between the three node |
| 61 | + // components again. |
| 62 | + const [first, ...rest] = centerOffsets |
| 63 | + for (const offset of rest) { |
| 64 | + expect(Math.abs(offset - first)).toBeLessThanOrEqual(1) |
| 65 | + } |
| 66 | +}) |
0 commit comments