openpencil/tests/e2e/panels/visual.spec.ts
Danila Poyarkov 3e750383c0 test(app): add panel UI guardrails
- Lock representative property and Layer Tree states with visual snapshots\n- Cover keyboard tab order and keyboard activation for panel controls\n- Reject template-time UI hooks and raw SVG app icons through parsed Vue ASTs
2026-07-17 23:53:06 +03:00

146 lines
4.4 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test'
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 reset() {
await canvas.clearCanvas()
await canvas.waitForRender()
}
test('single rectangle panel', async () => {
await reset()
await page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const node = store.graph.createNode('RECTANGLE', store.state.currentPageId, {
name: 'Card',
x: 100,
y: 100,
width: 180,
height: 120,
cornerRadius: 16
})
store.select([node.id])
})
await canvas.waitForRender()
await expect(page.getByTestId('design-panel-single')).toHaveScreenshot('single-rectangle.png')
})
test('text selection panel', async () => {
await reset()
await page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const node = store.graph.createNode('TEXT', store.state.currentPageId, {
name: 'Heading',
text: 'Panel typography',
x: 100,
y: 100,
width: 240,
height: 80,
fontSize: 24
})
store.select([node.id])
})
await canvas.waitForRender()
await expect(page.getByTestId('design-panel-single')).toHaveScreenshot('text-selection.png')
})
test('mixed selection panel', async () => {
await reset()
await page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const first = store.graph.createNode('RECTANGLE', store.state.currentPageId, {
x: 100,
y: 100,
width: 120,
height: 80
})
const second = store.graph.createNode('ELLIPSE', store.state.currentPageId, {
x: 260,
y: 100,
width: 100,
height: 100
})
store.select([first.id, second.id])
})
await canvas.waitForRender()
await expect(page.getByTestId('design-panel-multi')).toHaveScreenshot('mixed-selection.png')
})
test('instance panel', async () => {
await reset()
await page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const component = store.graph.createNode('COMPONENT', store.state.currentPageId, {
name: 'Button',
componentPropertyDefinitions: [
{ id: '40:1', name: 'Label', type: 'TEXT', defaultValue: 'Button' },
{ id: '40:2', name: 'Enabled', type: 'BOOLEAN', defaultValue: 'true' }
]
})
store.graph.createNode('TEXT', component.id, {
name: 'Label',
text: 'Button',
componentPropertyReferences: [{ propertyId: '40:1', field: 'TEXT' }]
})
store.graph.createNode('FRAME', component.id, {
name: 'State',
componentPropertyReferences: [{ propertyId: '40:2', field: 'VISIBLE' }]
})
const instance = store.graph.createInstance(component.id, store.state.currentPageId)
if (!instance) throw new Error('Expected instance')
store.select([instance.id])
})
await canvas.waitForRender()
await expect(page.getByTestId('design-panel-single')).toHaveScreenshot('instance.png')
})
test('empty canvas panel', async () => {
await reset()
await expect(page.getByTestId('design-panel-empty')).toHaveScreenshot('empty-canvas.png')
})
test('nested groups layer tree', async () => {
await reset()
await page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const outer = store.graph.createNode('GROUP', store.state.currentPageId, {
name: 'Outer group',
expanded: true
})
const inner = store.graph.createNode('GROUP', outer.id, {
name: 'Inner group',
expanded: true
})
store.graph.createNode('RECTANGLE', inner.id, { name: 'Nested rectangle' })
store.select([inner.id])
})
await canvas.waitForRender()
await page
.getByTestId('layers-item')
.filter({ hasText: 'Inner group' })
.locator('[data-slot="disclosure"]')
.click()
await expect(page.getByTestId('layers-panel')).toHaveScreenshot('nested-groups-layer-tree.png')
})