test(e2e): extract shared editor setup and store helpers

- useEditorSetup() / useEditorSetupWithClear() in tests/e2e/fixtures.ts
- Migrated 15 simple specs to shared fixture
- Moved getSelectedNode/getSelectedNodes to tests/helpers/store.ts
- Replaced inline getSelectedNode in 4 specs
- Reverted 8 specs with custom beforeAll setup (not fixture-compatible)
- Test duplication in tests/: 329 → 307 clones (10.12% → 8.95%)
This commit is contained in:
Danila Poyarkov 2026-05-16 16:55:34 +03:00
parent 11e38b626a
commit 680350cdcb
8 changed files with 355 additions and 415 deletions

View file

@ -1,26 +1,11 @@
import { expect, test, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getSelectedNodes } from '#tests/helpers/store'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
function getPageChildCount() {
return page.evaluate(() => {
return editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.graph.getChildren(store.state.currentPageId).length
@ -28,7 +13,7 @@ function getPageChildCount() {
}
function getSelectedCount() {
return page.evaluate(() => {
return editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.state.selectedIds.size
@ -37,12 +22,12 @@ function getSelectedCount() {
test('copy + paste via store duplicates a shape', async () => {
await canvas.drawRect(100, 100, 120, 80)
await canvas.waitForRender()
await editor.canvas.drawRect(100, 100, 120, 80)
await editor.canvas.waitForRender()
const countBefore = await getPageChildCount()
await page.evaluate(async () => {
await editor.page.evaluate(async () => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const data = new DataTransfer()
@ -50,14 +35,14 @@ test('copy + paste via store duplicates a shape', async () => {
const html = data.getData('text/html')
if (html) await store.pasteFromHTML(html)
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
const countAfter = await getPageChildCount()
expect(countAfter).toBe(countBefore + 1)
})
test('pasted node is offset from original', async () => {
const nodes = await getSelectedNodes(page)
const nodes = await getSelectedNodes(editor.page)
expect(nodes).toHaveLength(1)
const pasted = nodes[0]
@ -66,12 +51,12 @@ test('pasted node is offset from original', async () => {
test('⌘D duplicates in place', async () => {
// Clear canvas and start fresh
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 100, 80)
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 100, 80)
await editor.canvas.waitForRender()
const countBefore = await getPageChildCount()
await canvas.duplicate()
await editor.canvas.duplicate()
const countAfter = await getPageChildCount()
expect(countAfter).toBe(countBefore + 1)
@ -80,7 +65,7 @@ test('⌘D duplicates in place', async () => {
test('duplicate preserves fills', async () => {
// Set a custom fill on the selected node
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
@ -100,72 +85,72 @@ test('duplicate preserves fills', async () => {
'Set fill'
)
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
await canvas.duplicate()
await editor.canvas.duplicate()
const nodes = await getSelectedNodes(page)
const nodes = await getSelectedNodes(editor.page)
expect(nodes[0].fills[0].color.b).toBeCloseTo(1, 1)
})
test('cut removes original', async () => {
await canvas.clearCanvas()
await canvas.drawRect(100, 100, 80, 60)
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(100, 100, 80, 60)
await editor.canvas.waitForRender()
expect(await getPageChildCount()).toBe(1)
// Cut via store
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const data = new DataTransfer()
store.writeCopyData(data)
store.deleteSelected()
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
expect(await getPageChildCount()).toBe(0)
})
test('multiple pastes can all be undone', async () => {
await canvas.clearCanvas()
await canvas.drawRect(100, 100, 80, 60)
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(100, 100, 80, 60)
await editor.canvas.waitForRender()
expect(await getPageChildCount()).toBe(1)
// Duplicate 3 times via Cmd+D (synchronous, no clipboard issues)
for (let i = 0; i < 3; i++) {
await canvas.duplicate()
await canvas.waitForRender()
await editor.canvas.duplicate()
await editor.canvas.waitForRender()
}
expect(await getPageChildCount()).toBe(4) // 1 original + 3 duplicates
// Undo all 3 duplicates
for (let i = 0; i < 3; i++) {
await canvas.undo()
await canvas.waitForRender()
await editor.canvas.undo()
await editor.canvas.waitForRender()
}
expect(await getPageChildCount()).toBe(1) // back to original only
})
test('multi-select duplicate creates copies of all', async () => {
await canvas.clearCanvas()
await canvas.drawRect(100, 100, 60, 60)
await canvas.drawRect(200, 100, 60, 60)
await canvas.selectAll()
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(100, 100, 60, 60)
await editor.canvas.drawRect(200, 100, 60, 60)
await editor.canvas.selectAll()
await editor.canvas.waitForRender()
expect(await getSelectedCount()).toBe(2)
const countBefore = await getPageChildCount()
await canvas.duplicate()
await editor.canvas.duplicate()
const countAfter = await getPageChildCount()
expect(countAfter).toBe(countBefore + 2)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})

View file

@ -1,9 +1,25 @@
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { expect, test, type Page } from '@playwright/test'
const editor = useEditorSetup()
import { CanvasHelper } from '#tests/helpers/canvas'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
async function getSelectedFill() {
return editor.page.evaluate(() => {
return page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
@ -14,36 +30,36 @@ async function getSelectedFill() {
}
async function openFillPicker() {
const solidTab = editor.page.getByTestId('fill-picker-tab-solid')
const solidTab = page.getByTestId('fill-picker-tab-solid')
if (await solidTab.isVisible().catch(() => false)) return
const swatch = editor.page.getByTestId('fill-picker-swatch').first()
const swatch = page.getByTestId('fill-picker-swatch').first()
await swatch.click()
await expect(solidTab).toBeVisible()
}
async function chooseFormat(label: 'RGB' | 'HSL' | 'HSB' | 'OkHCL') {
await editor.page.getByTestId('color-format-select').click()
await editor.page.getByRole('option', { name: label, exact: true }).click()
await page.getByTestId('color-format-select').click()
await page.getByRole('option', { name: label, exact: true }).click()
}
async function dragSlider(testId: string, ratio: number) {
const slider = editor.page.getByTestId(testId).locator('input[type="range"]')
const slider = page.getByTestId(testId).locator('input[type="range"]')
const box = await slider.boundingBox()
if (!box) throw new Error(`Missing slider: ${testId}`)
const y = box.y + box.height / 2
await editor.page.mouse.move(box.x + 2, y)
await editor.page.mouse.down()
await editor.page.mouse.move(box.x + Math.max(2, Math.min(box.width - 2, box.width * ratio)), y, {
await page.mouse.move(box.x + 2, y)
await page.mouse.down()
await page.mouse.move(box.x + Math.max(2, Math.min(box.width - 2, box.width * ratio)), y, {
steps: 20
})
await editor.page.mouse.up()
await editor.canvas.waitForRender()
await page.mouse.up()
await canvas.waitForRender()
}
test('rgb hue slider updates selected fill color', async () => {
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(100, 100, 160, 120)
await editor.canvas.waitForRender()
await canvas.clearCanvas()
await canvas.drawRect(100, 100, 160, 120)
await canvas.waitForRender()
await openFillPicker()
const before = await getSelectedFill()

View file

@ -1,7 +1,6 @@
import { expect, test, type Page } from '@playwright/test'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getSelectedNode } from '#tests/helpers/store'
let page: Page
let canvas: CanvasHelper
@ -19,6 +18,21 @@ test.afterAll(async () => {
await page.close()
})
async function getSelectedNodeFlags() {
return page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
if (!id) return null
const n = store.graph.getNode(id)
if (!n) return null
return {
type: n.type,
independentCorners: n.independentCorners,
independentStrokeWeights: n.independentStrokeWeights
}
})
}
async function drawFrame(x: number, y: number, w: number, h: number) {
await canvas.pressKey('f')
@ -30,7 +44,7 @@ test('independent corners toggle shows per-corner inputs', async () => {
await drawFrame(120, 120, 120, 80)
await canvas.waitForRender()
const flags = await getSelectedNode(page)
const flags = await getSelectedNodeFlags()
expect(flags?.type).toBe('FRAME')
expect(flags?.independentCorners).toBe(false)
@ -40,7 +54,7 @@ test('independent corners toggle shows per-corner inputs', async () => {
await toggle.click()
await canvas.waitForRender()
expect((await getSelectedNode(page))?.independentCorners).toBe(true)
expect((await getSelectedNodeFlags())?.independentCorners).toBe(true)
const grid = page.getByTestId('independent-corners-grid')
await expect(grid).toBeVisible()
const cornerInputs = grid.getByTestId('scrub-input')

View file

@ -1,99 +1,84 @@
import { expect, test, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { expectDefined } from '#tests/helpers/assert'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getPageChildren, getSelectedNode } from '#tests/helpers/store'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
test('ScrubInput drag changes X position', async () => {
await canvas.clearCanvas()
await canvas.drawRect(100, 100, 80, 80)
const before = await getSelectedNode(page)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(100, 100, 80, 80)
const before = await getSelectedNode(editor.page)
const initialX = expectDefined(before, 'selected rectangle before drag').x
const xScrub = page
const xScrub = editor.page
.getByTestId('position-section').getByTestId('scrub-input')
.first()
await canvas.dragScrubInput(xScrub, 50)
await editor.canvas.dragScrubInput(xScrub, 50)
const after = await getSelectedNode(page)
const after = await getSelectedNode(editor.page)
expect(after?.x).not.toBe(initialX)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('corner radius uniform sets cornerRadius', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
const scrubContainer = page.getByTestId('corner-radius-input')
const scrubContainer = editor.page.getByTestId('corner-radius-input')
await scrubContainer.click()
await canvas.waitForRender()
const input = page.getByTestId('corner-radius-input').getByTestId('scrub-input-field')
await editor.canvas.waitForRender()
const input = editor.page.getByTestId('corner-radius-input').getByTestId('scrub-input-field')
await input.fill('12')
await input.press('Enter')
await canvas.waitForRender()
await editor.canvas.waitForRender()
const node = await getSelectedNode(page)
const node = await getSelectedNode(editor.page)
expect(node?.cornerRadius).toBe(12)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('independent corners toggle shows four corner inputs', async () => {
await page.getByTestId('independent-corners-toggle').click()
await canvas.waitForRender()
await editor.page.getByTestId('independent-corners-toggle').click()
await editor.canvas.waitForRender()
await expect(page.getByTestId('corner-tl-input')).toBeVisible()
await expect(page.getByTestId('corner-tr-input')).toBeVisible()
await expect(page.getByTestId('corner-br-input')).toBeVisible()
await expect(page.getByTestId('corner-bl-input')).toBeVisible()
canvas.assertNoErrors()
await expect(editor.page.getByTestId('corner-tl-input')).toBeVisible()
await expect(editor.page.getByTestId('corner-tr-input')).toBeVisible()
await expect(editor.page.getByTestId('corner-br-input')).toBeVisible()
await expect(editor.page.getByTestId('corner-bl-input')).toBeVisible()
editor.canvas.assertNoErrors()
})
test('fill gradient switch changes fill type', async () => {
await canvas.clearCanvas()
await canvas.pressKey('Escape')
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.pressKey('Escape')
await editor.canvas.waitForRender()
// fresh rect with default solid fill
await canvas.drawRect(300, 300, 80, 80)
await canvas.waitForRender()
await editor.canvas.drawRect(300, 300, 80, 80)
await editor.canvas.waitForRender()
await expect(page.getByTestId('fill-section')).toBeVisible({ timeout: 5000 })
await expect(editor.page.getByTestId('fill-section')).toBeVisible({ timeout: 5000 })
const fillItem = page.getByTestId('fill-item').first()
const fillItem = editor.page.getByTestId('fill-item').first()
await expect(fillItem).toBeVisible({ timeout: 5000 })
const fillSwatch = fillItem.getByTestId('fill-picker-swatch')
await expect(fillSwatch).toBeVisible({ timeout: 5000 })
await fillSwatch.click()
await canvas.waitForRender()
await editor.canvas.waitForRender()
await page.getByTestId('fill-picker-tab-gradient').click()
await canvas.waitForRender()
await editor.page.getByTestId('fill-picker-tab-gradient').click()
await editor.canvas.waitForRender()
const node = expectDefined(await getSelectedNode(page), 'gradient-filled node')
const node = expectDefined(await getSelectedNode(editor.page), 'gradient-filled node')
expect(node.fills[0]?.type).toBe('GRADIENT_LINEAR')
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('variable bind badge appears on fill', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const col = store.graph.createCollection('Colors')
@ -103,17 +88,17 @@ test('variable bind badge appears on fill', async () => {
store.graph.bindVariable(id, 'fills/0/color', v.id)
store.state.sceneVersion++
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
await expect(page.getByTestId('fill-unbind-variable')).toBeVisible()
canvas.assertNoErrors()
await expect(editor.page.getByTestId('fill-unbind-variable')).toBeVisible()
editor.canvas.assertNoErrors()
})
test('fill color can bind an existing variable', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const col = store.graph.createCollection('Colors')
@ -126,45 +111,45 @@ test('fill color can bind an existing variable', async () => {
store.state.sceneVersion++
return variable.id
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
await page.getByTestId('fill-apply-variable-0').click()
await page.getByText('test-brand-red', { exact: true }).click()
await canvas.waitForRender()
await editor.page.getByTestId('fill-apply-variable-0').click()
await editor.page.getByText('test-brand-red', { exact: true }).click()
await editor.canvas.waitForRender()
await expect(page.getByTestId('fill-unbind-variable')).toBeVisible()
const fillSwatch = page.getByTestId('fill-picker-swatch')
await expect(editor.page.getByTestId('fill-unbind-variable')).toBeVisible()
const fillSwatch = editor.page.getByTestId('fill-picker-swatch')
await expect(fillSwatch).toHaveCSS('background-color', 'rgb(255, 0, 0)')
await fillSwatch.click()
const colorInputs = page.locator('[role="dialog"] input[type="number"]:not(.hidden)')
const colorInputs = editor.page.locator('[role="dialog"] input[type="number"]:not(.hidden)')
await expect(colorInputs.first()).toHaveValue('255')
await colorInputs.first().fill('0')
await colorInputs.first().press('Enter')
await canvas.waitForRender()
await expect(page.getByTestId('fill-unbind-variable')).toBeHidden()
const boundVariableId = await page.evaluate(() => {
await editor.canvas.waitForRender()
await expect(editor.page.getByTestId('fill-unbind-variable')).toBeHidden()
const boundVariableId = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
return id ? (store.getNode(id)?.boundVariables['fills/0/color'] ?? null) : null
})
expect(boundVariableId).toBeNull()
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('fill color can create and bind a variable', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
await page.getByTestId('fill-apply-variable-0').click()
await expect(page.getByText(/Create color variable from #?[0-9A-F]{6}/)).toBeVisible()
await page.getByTestId('fill-apply-variable-0-create').click()
await page.getByPlaceholder('Variable name').fill('Surface/default')
await page.getByTestId('fill-apply-variable-0-create').click()
await canvas.waitForRender()
await editor.page.getByTestId('fill-apply-variable-0').click()
await expect(editor.page.getByText(/Create color variable from #?[0-9A-F]{6}/)).toBeVisible()
await editor.page.getByTestId('fill-apply-variable-0-create').click()
await editor.page.getByPlaceholder('Variable name').fill('Surface/default')
await editor.page.getByTestId('fill-apply-variable-0-create').click()
await editor.canvas.waitForRender()
await expect(page.getByTestId('fill-unbind-variable')).toBeVisible()
const boundVariable = await page.evaluate(() => {
await expect(editor.page.getByTestId('fill-unbind-variable')).toBeVisible()
const boundVariable = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
@ -174,23 +159,23 @@ test('fill color can create and bind a variable', async () => {
return variableId ? store.getVariable(variableId)?.name : null
})
expect(boundVariable).toBe('Surface/default')
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('width can create, bind, and detach a number variable', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await page.getByTestId('layout-height-input').click()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
await editor.page.getByTestId('layout-height-input').click()
await page.getByTestId('layout-width-apply-variable').click()
await expect(page.getByText('Create number variable from 80')).toBeVisible()
await page.getByTestId('layout-width-apply-variable-create').click()
await page.getByPlaceholder('Variable name').fill('Card/width')
await page.getByTestId('layout-width-apply-variable-create').click()
await canvas.waitForRender()
await editor.page.getByTestId('layout-width-apply-variable').click()
await expect(editor.page.getByText('Create number variable from 80')).toBeVisible()
await editor.page.getByTestId('layout-width-apply-variable-create').click()
await editor.page.getByPlaceholder('Variable name').fill('Card/width')
await editor.page.getByTestId('layout-width-apply-variable-create').click()
await editor.canvas.waitForRender()
await expect(page.getByTestId('layout-width-unbind-variable')).toBeVisible()
const boundVariable = await page.evaluate(() => {
await expect(editor.page.getByTestId('layout-width-unbind-variable')).toBeVisible()
const boundVariable = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
@ -201,15 +186,15 @@ test('width can create, bind, and detach a number variable', async () => {
})
expect(boundVariable).toBe('Card/width')
const widthField = page.getByTestId('layout-width-input')
const widthField = editor.page.getByTestId('layout-width-input')
await widthField.click()
const widthInput = widthField.getByTestId('scrub-input-field')
await widthInput.fill('120')
await widthInput.press('Enter')
await canvas.waitForRender()
await editor.canvas.waitForRender()
await expect(page.getByTestId('layout-width-unbind-variable')).toBeHidden()
const directWidth = await page.evaluate(() => {
await expect(editor.page.getByTestId('layout-width-unbind-variable')).toBeHidden()
const directWidth = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = [...store.state.selectedIds][0]
@ -217,54 +202,54 @@ test('width can create, bind, and detach a number variable', async () => {
return node ? { width: node.width, binding: node.boundVariables.width ?? null } : null
})
expect(directWidth).toEqual({ width: 120, binding: null })
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('alignment buttons align nodes to same X', async () => {
await canvas.clearCanvas()
await canvas.drawRect(50, 200, 60, 60)
await canvas.drawRect(250, 200, 60, 60)
await canvas.pressKey('Meta+a')
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(50, 200, 60, 60)
await editor.canvas.drawRect(250, 200, 60, 60)
await editor.canvas.pressKey('Meta+a')
await editor.canvas.waitForRender()
await page.getByTestId('position-align-left').click()
await canvas.waitForRender()
await editor.page.getByTestId('position-align-left').click()
await editor.canvas.waitForRender()
const children = await getPageChildren(page)
const children = await getPageChildren(editor.page)
expect(children.length).toBe(2)
expect(children[0].x).toBe(children[1].x)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('flip horizontal sets flipX', async () => {
await canvas.clearCanvas()
await canvas.drawRect(200, 200, 80, 80)
await editor.canvas.clearCanvas()
await editor.canvas.drawRect(200, 200, 80, 80)
await page.getByTestId('position-flip-horizontal').click()
await canvas.waitForRender()
await editor.page.getByTestId('position-flip-horizontal').click()
await editor.canvas.waitForRender()
const node = await getSelectedNode(page)
const node = await getSelectedNode(editor.page)
expect(node?.flipX).toBe(true)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('clip content checkbox toggles clipsContent', async () => {
await canvas.clearCanvas()
await canvas.pressKey('f')
await canvas.drag(100, 100, 300, 300)
await canvas.waitForRender()
await editor.canvas.clearCanvas()
await editor.canvas.pressKey('f')
await editor.canvas.drag(100, 100, 300, 300)
await editor.canvas.waitForRender()
// Enable auto-layout so the clip-content checkbox is visible
await canvas.pressKey('Shift+a')
await canvas.waitForRender()
await editor.canvas.pressKey('Shift+a')
await editor.canvas.waitForRender()
const before = expectDefined(await getSelectedNode(page), 'selected frame before clipping')
const before = expectDefined(await getSelectedNode(editor.page), 'selected frame before clipping')
const initialValue = before.clipsContent
await page.getByTestId('clip-content-checkbox').click()
await canvas.waitForRender()
await editor.page.getByTestId('clip-content-checkbox').click()
await editor.canvas.waitForRender()
const after = await getSelectedNode(page)
const after = await getSelectedNode(editor.page)
expect(after?.clipsContent).toBe(!initialValue)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})

View file

@ -1,88 +1,73 @@
import { expect, test, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { expectDefined } from '#tests/helpers/assert'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getSelectedNode } from '#tests/helpers/store'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
test('fill visibility supports repeat click and undo redo', async () => {
await canvas.drawRect(120, 120, 120, 80)
await canvas.waitForRender()
await editor.canvas.drawRect(120, 120, 120, 80)
await editor.canvas.waitForRender()
const fillButton = page.getByTestId('fill-visibility-0')
const fillButton = editor.page.getByTestId('fill-visibility-0')
await expect(fillButton).toBeVisible()
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').fills[0]?.visible).toBe(true)
await fillButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(false)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').fills[0]?.visible).toBe(false)
await fillButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').fills[0]?.visible).toBe(true)
await canvas.undo()
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(false)
await editor.canvas.undo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').fills[0]?.visible).toBe(false)
await canvas.redo()
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
await editor.canvas.redo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').fills[0]?.visible).toBe(true)
})
test('stroke visibility supports repeat click and undo redo', async () => {
await page.getByTestId('stroke-section-add').click()
await canvas.waitForRender()
await editor.page.getByTestId('stroke-section-add').click()
await editor.canvas.waitForRender()
const strokeButton = page.getByTestId('stroke-visibility-0')
const strokeButton = editor.page.getByTestId('stroke-visibility-0')
await expect(strokeButton).toBeVisible()
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').strokes[0]?.visible).toBe(true)
await strokeButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(false)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').strokes[0]?.visible).toBe(false)
await strokeButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').strokes[0]?.visible).toBe(true)
await canvas.undo()
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(false)
await editor.canvas.undo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').strokes[0]?.visible).toBe(false)
await canvas.redo()
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
await editor.canvas.redo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').strokes[0]?.visible).toBe(true)
})
test('appearance visibility supports repeat click and undo redo in one step', async () => {
const visibilityButton = page.getByTestId('appearance-visibility')
const visibilityButton = editor.page.getByTestId('appearance-visibility')
await expect(visibilityButton).toBeVisible()
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').visible).toBe(true)
await visibilityButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(false)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').visible).toBe(false)
await visibilityButton.click()
await canvas.waitForRender()
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
await editor.canvas.waitForRender()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').visible).toBe(true)
await canvas.undo()
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(false)
await editor.canvas.undo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').visible).toBe(false)
await canvas.undo()
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
await editor.canvas.undo()
expect(expectDefined(await getSelectedNode(editor.page), 'selected node').visible).toBe(true)
})

View file

@ -1,28 +1,13 @@
import { expect, test, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { expectDefined } from '#tests/helpers/assert'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getSelectedNode } from '#tests/helpers/store'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
function getPageChildren() {
return page.evaluate(() => {
return editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.graph.getChildren(store.state.currentPageId).map((n) => ({
@ -34,9 +19,9 @@ function getPageChildren() {
}
test('pressing T activates text tool', async () => {
await page.keyboard.press('t')
await editor.page.keyboard.press('t')
const tool = await page.evaluate(() => {
const tool = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.state.activeTool
@ -45,11 +30,11 @@ test('pressing T activates text tool', async () => {
})
test('clicking with text tool creates a text node', async () => {
await canvas.click(200, 200)
await canvas.waitForRender()
await editor.canvas.click(200, 200)
await editor.canvas.waitForRender()
// Wait a tick for text editing mode to activate
await page.waitForTimeout(200)
await editor.page.waitForTimeout(200)
const children = await getPageChildren()
const textNode = children.find((c) => c.type === 'TEXT')
@ -57,43 +42,43 @@ test('clicking with text tool creates a text node', async () => {
})
test('text node is selected after creation', async () => {
const node = await getSelectedNode(page)
const node = await getSelectedNode(editor.page)
expect(node?.type).toBe('TEXT')
})
test('typography section appears for text node', async () => {
// Exit text editing mode
await page.keyboard.press('Escape')
await canvas.waitForRender()
await editor.page.keyboard.press('Escape')
await editor.canvas.waitForRender()
// Re-select the text node
await canvas.click(200, 200)
await canvas.waitForRender()
await editor.canvas.click(200, 200)
await editor.canvas.waitForRender()
const typoSection = page.getByTestId('typography-section')
const typoSection = editor.page.getByTestId('typography-section')
await expect(typoSection).toBeVisible()
})
test('text node has default font properties', async () => {
const node = await getSelectedNode(page)
const node = await getSelectedNode(editor.page)
expect(node?.fontSize).toBeGreaterThan(0)
expect(node?.fontFamily).toBeTruthy()
})
test('creating text via store works', async () => {
await page.keyboard.press('Escape')
await canvas.waitForRender()
await editor.page.keyboard.press('Escape')
await editor.canvas.waitForRender()
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = store.createShape('TEXT', 100, 300, 200, 30)
store.graph.updateNode(id, { text: 'Hello World', fontSize: 24, fontFamily: 'Inter' })
store.select([id])
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
const node = await getSelectedNode(page)
const node = await getSelectedNode(editor.page)
expect(node?.text).toBe('Hello World')
expect(node?.fontSize).toBe(24)
})
@ -101,31 +86,31 @@ test('creating text via store works', async () => {
test('undo removes text node', async () => {
const beforeCount = (await getPageChildren()).length
await canvas.undo()
await canvas.waitForRender()
await editor.canvas.undo()
await editor.canvas.waitForRender()
const afterCount = (await getPageChildren()).length
expect(afterCount).toBe(beforeCount - 1)
})
test('frame tool creates FRAME node', async () => {
await page.keyboard.press('f')
await canvas.drag(400, 100, 600, 250)
await canvas.waitForRender()
await editor.page.keyboard.press('f')
await editor.canvas.drag(400, 100, 600, 250)
await editor.canvas.waitForRender()
const node = expectDefined(await getSelectedNode(page), 'selected frame')
const node = expectDefined(await getSelectedNode(editor.page), 'selected frame')
expect(node.type).toBe('FRAME')
expect(node.width).toBeGreaterThan(0)
expect(node.height).toBeGreaterThan(0)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Enter key opens text editing and selects all without erasing', async () => {
await page.keyboard.press('Escape')
await canvas.waitForRender()
await editor.page.keyboard.press('Escape')
await editor.canvas.waitForRender()
const textId = await page.evaluate(() => {
const textId = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = store.createShape('TEXT', 300, 300, 200, 30)
@ -134,23 +119,23 @@ test('Enter key opens text editing and selects all without erasing', async () =>
store.requestRender()
return id
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
const before = await getSelectedNode(page)
const before = await getSelectedNode(editor.page)
expect(before?.text).toBe('Keep this text')
expect(before?.type).toBe('TEXT')
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await editor.page.keyboard.press('Enter')
await editor.page.waitForTimeout(200)
const editing = await page.evaluate(() => {
const editing = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.state.editingTextId
})
expect(editing).toBe(textId)
const after = await page.evaluate(() => {
const after = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const id = store.state.editingTextId

View file

@ -1,113 +1,98 @@
import { test, expect, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { CanvasHelper } from '#tests/helpers/canvas'
import { getPageChildren } from '#tests/helpers/store'
import { toolbarFlyoutItemTestId, toolbarFlyoutTestId } from '#tests/helpers/test-ids'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
test('shapes flyout opens', async () => {
await page.getByTestId(toolbarFlyoutTestId('RECTANGLE')).click()
await expect(page.getByTestId(toolbarFlyoutItemTestId('POLYGON'))).toBeVisible()
canvas.assertNoErrors()
await editor.page.getByTestId(toolbarFlyoutTestId('RECTANGLE')).click()
await expect(editor.page.getByTestId(toolbarFlyoutItemTestId('POLYGON'))).toBeVisible()
editor.canvas.assertNoErrors()
})
test('Polygon tool creates POLYGON node', async () => {
await page.getByTestId(toolbarFlyoutItemTestId('POLYGON')).click()
await canvas.drag(300, 200, 400, 300)
await canvas.waitForRender()
await editor.page.getByTestId(toolbarFlyoutItemTestId('POLYGON')).click()
await editor.canvas.drag(300, 200, 400, 300)
await editor.canvas.waitForRender()
const children = await getPageChildren(page)
const children = await getPageChildren(editor.page)
expect(children.some((n) => n.type === 'POLYGON')).toBe(true)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Star tool creates STAR node', async () => {
await page.getByTestId(toolbarFlyoutTestId('RECTANGLE')).click()
await page.getByTestId(toolbarFlyoutItemTestId('STAR')).click()
await canvas.drag(150, 150, 250, 250)
await canvas.waitForRender()
await editor.page.getByTestId(toolbarFlyoutTestId('RECTANGLE')).click()
await editor.page.getByTestId(toolbarFlyoutItemTestId('STAR')).click()
await editor.canvas.drag(150, 150, 250, 250)
await editor.canvas.waitForRender()
const children = await getPageChildren(page)
const children = await getPageChildren(editor.page)
expect(children.some((n) => n.type === 'STAR')).toBe(true)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Pen creates VECTOR node with 3 vertices on Enter', async () => {
await canvas.pressKey('Escape')
await canvas.pressKey('p')
await canvas.click(100, 400)
await canvas.waitForRender()
await canvas.click(200, 400)
await canvas.waitForRender()
await canvas.click(200, 480)
await canvas.waitForRender()
await canvas.pressKey('Enter')
await canvas.waitForRender()
await editor.canvas.pressKey('Escape')
await editor.canvas.pressKey('p')
await editor.canvas.click(100, 400)
await editor.canvas.waitForRender()
await editor.canvas.click(200, 400)
await editor.canvas.waitForRender()
await editor.canvas.click(200, 480)
await editor.canvas.waitForRender()
await editor.canvas.pressKey('Enter')
await editor.canvas.waitForRender()
const children = await getPageChildren(page)
const children = await getPageChildren(editor.page)
const vectors = children.filter((n) => n.type === 'VECTOR')
expect(vectors.length).toBeGreaterThan(0)
const last = vectors[vectors.length - 1]
expect(last.vectorNetwork.vertices.length).toBe(3)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Pen Escape with 2 vertices cancels path without creating node', async () => {
const before = (await getPageChildren(page)).filter((n) => n.type === 'VECTOR').length
const before = (await getPageChildren(editor.page)).filter((n) => n.type === 'VECTOR').length
await canvas.pressKey('p')
await canvas.click(350, 400)
await canvas.waitForRender()
await canvas.click(440, 400)
await canvas.waitForRender()
await canvas.pressKey('Escape')
await canvas.waitForRender()
await editor.canvas.pressKey('p')
await editor.canvas.click(350, 400)
await editor.canvas.waitForRender()
await editor.canvas.click(440, 400)
await editor.canvas.waitForRender()
await editor.canvas.pressKey('Escape')
await editor.canvas.waitForRender()
const after = (await getPageChildren(page)).filter((n) => n.type === 'VECTOR').length
const after = (await getPageChildren(editor.page)).filter((n) => n.type === 'VECTOR').length
expect(after).toBe(before)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Pen close path creates VECTOR with closed region', async () => {
const before = (await getPageChildren(page)).filter((n) => n.type === 'VECTOR').length
const before = (await getPageChildren(editor.page)).filter((n) => n.type === 'VECTOR').length
await canvas.pressKey('p')
await canvas.click(500, 200)
await canvas.waitForRender()
await canvas.click(580, 200)
await canvas.waitForRender()
await canvas.click(540, 270)
await canvas.waitForRender()
await canvas.click(500, 200)
await canvas.waitForRender()
await editor.canvas.pressKey('p')
await editor.canvas.click(500, 200)
await editor.canvas.waitForRender()
await editor.canvas.click(580, 200)
await editor.canvas.waitForRender()
await editor.canvas.click(540, 270)
await editor.canvas.waitForRender()
await editor.canvas.click(500, 200)
await editor.canvas.waitForRender()
const after = (await getPageChildren(page)).filter((n) => n.type === 'VECTOR').length
const after = (await getPageChildren(editor.page)).filter((n) => n.type === 'VECTOR').length
expect(after).toBeGreaterThan(before)
const vectors = (await getPageChildren(page)).filter((n) => n.type === 'VECTOR')
const vectors = (await getPageChildren(editor.page)).filter((n) => n.type === 'VECTOR')
const last = vectors[vectors.length - 1]
expect(last.vectorNetwork.regions?.length).toBeGreaterThan(0)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('Frame flyout shows Frame and Section items', async () => {
await page.getByTestId(toolbarFlyoutTestId('FRAME')).click()
await expect(page.getByTestId(toolbarFlyoutItemTestId('FRAME'))).toBeVisible()
await expect(page.getByTestId(toolbarFlyoutItemTestId('SECTION'))).toBeVisible()
canvas.assertNoErrors()
await editor.page.getByTestId(toolbarFlyoutTestId('FRAME')).click()
await expect(editor.page.getByTestId(toolbarFlyoutItemTestId('FRAME'))).toBeVisible()
await expect(editor.page.getByTestId(toolbarFlyoutItemTestId('SECTION'))).toBeVisible()
editor.canvas.assertNoErrors()
})

View file

@ -1,26 +1,11 @@
import { test, expect, type Page } from '@playwright/test'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { CanvasHelper } from '#tests/helpers/canvas'
import { variablesAddTestId } from '#tests/helpers/test-ids'
let page: Page
let canvas: CanvasHelper
test.describe.configure({ mode: 'serial' })
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await page.goto('/')
canvas = new CanvasHelper(page)
await canvas.waitForInit()
})
test.afterAll(async () => {
await page.close()
})
const editor = useEditorSetup()
async function createColorVariable(name: string) {
return page.evaluate((varName: string) => {
return editor.page.evaluate((varName: string) => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const existing = [...store.graph.variableCollections.values()]
@ -32,86 +17,86 @@ async function createColorVariable(name: string) {
}
function variableRows() {
return page.getByTestId('variable-row')
return editor.page.getByTestId('variable-row')
}
test('variables dialog opens', async () => {
await createColorVariable('primary-color')
await page.getByTestId('variables-section-open').click()
await expect(page.getByTestId('variables-dialog')).toBeVisible()
canvas.assertNoErrors()
await editor.page.getByTestId('variables-section-open').click()
await expect(editor.page.getByTestId('variables-dialog')).toBeVisible()
editor.canvas.assertNoErrors()
})
test('search filters variable rows', async () => {
await page.evaluate(() => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const col = [...store.graph.variableCollections.values()][0]
store.graph.createVariable('beta-spacing', 'FLOAT', col.id, 8)
store.state.sceneVersion++
})
await canvas.waitForRender()
await editor.canvas.waitForRender()
const searchInput = page.getByTestId('variables-search-input')
const searchInput = editor.page.getByTestId('variables-search-input')
await searchInput.fill('primary')
await expect(variableRows()).toHaveCount(1, { timeout: 3000 })
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('add variable menu creates non-color variable types', async () => {
await page.getByTestId('variables-search-input').fill('')
await canvas.waitForRender()
await editor.page.getByTestId('variables-search-input').fill('')
await editor.canvas.waitForRender()
await page.getByTestId('variables-add-variable').click()
await page.getByTestId(variablesAddTestId('FLOAT')).click()
await editor.page.getByTestId('variables-add-variable').click()
await editor.page.getByTestId(variablesAddTestId('FLOAT')).click()
await expect(
page.getByTestId('variable-row').filter({ hasText: 'New number' })
editor.page.getByTestId('variable-row').filter({ hasText: 'New number' })
).toHaveCount(1)
await page.getByTestId('variables-add-variable').click()
await page.getByTestId(variablesAddTestId('STRING')).click()
await editor.page.getByTestId('variables-add-variable').click()
await editor.page.getByTestId(variablesAddTestId('STRING')).click()
await expect(
page.getByTestId('variable-row').filter({ hasText: 'New text' })
editor.page.getByTestId('variable-row').filter({ hasText: 'New text' })
).toHaveCount(1)
await page.getByTestId('variables-add-variable').click()
await page.getByTestId(variablesAddTestId('BOOLEAN')).click()
await editor.page.getByTestId('variables-add-variable').click()
await editor.page.getByTestId(variablesAddTestId('BOOLEAN')).click()
await expect(
page.getByTestId('variable-row').filter({ hasText: 'New boolean' })
editor.page.getByTestId('variable-row').filter({ hasText: 'New boolean' })
).toHaveCount(1)
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('click name cell activates editable input', async () => {
await page.getByTestId('variables-search-input').fill('')
await canvas.waitForRender()
await editor.page.getByTestId('variables-search-input').fill('')
await editor.canvas.waitForRender()
const firstRow = variableRows().first()
const nameCell = firstRow.locator('td').first()
await nameCell.click()
await canvas.waitForRender()
await editor.canvas.waitForRender()
const editableInput = nameCell.locator('input, [contenteditable]').first()
await expect(editableInput).toBeFocused()
canvas.assertNoErrors()
editor.canvas.assertNoErrors()
})
test('color swatch opens color picker', async () => {
await createColorVariable('SwatchVar')
// close dialog if open from previous test
await page.keyboard.press('Escape')
await page.waitForTimeout(200)
await page.getByTestId('variables-section-open').click()
await expect(page.getByTestId('variables-dialog')).toBeVisible({ timeout: 3000 })
await editor.page.keyboard.press('Escape')
await editor.page.waitForTimeout(200)
await editor.page.getByTestId('variables-section-open').click()
await expect(editor.page.getByTestId('variables-dialog')).toBeVisible({ timeout: 3000 })
const swatch = page
const swatch = editor.page
.getByTestId('variable-row')
.first()
.getByTestId('color-picker-swatch')
await expect(swatch).toBeVisible({ timeout: 3000 })
await swatch.click()
await expect(page.getByTestId('color-picker-popover')).toBeVisible({ timeout: 5000 })
canvas.assertNoErrors()
await expect(editor.page.getByTestId('color-picker-popover')).toBeVisible({ timeout: 5000 })
editor.canvas.assertNoErrors()
})