import { expect, test, type Locator, type Page } from '@playwright/test' async function dragBy(page: Page, locator: Locator, deltaX: number, deltaY: number) { const box = await locator.boundingBox() if (!box) throw new Error('Drag target is not visible') const start = { x: box.x + Math.min(30, box.width / 3), y: box.y + Math.min(14, box.height / 4) } await page.mouse.move(start.x, start.y) await page.mouse.down() await page.mouse.move(start.x + deltaX / 2, start.y + deltaY / 2, { steps: 3 }) await page.mouse.move(start.x + deltaX, start.y + deltaY, { steps: 3 }) await page.mouse.up() } async function boardPosition(locator: Locator) { return locator.evaluate(element => ({ left: Number.parseFloat((element as HTMLElement).style.left), top: Number.parseFloat((element as HTMLElement).style.top), })) } async function waitForSave(page: Page, action: () => Promise) { const response = page.waitForResponse(candidate => candidate.request().method() === 'PUT' && candidate.url().includes('/api/levels/') && candidate.ok()) await action() await response } test('move, folder expansion, hand pan, pinch zoom, and reload persistence', async ({ page }) => { await page.goto('/?level=e2e-level&edit=1') await expect(page.getByRole('heading', { name: 'Browser Safety Test' })).toBeVisible() const folder = page.locator('[data-temporal-id="widget:11111111-1111-4111-8111-111111111111"]') const file = page.locator('[data-temporal-id="file:contains:11111111-1111-4111-8111-111111111111:22222222-2222-4222-8222-222222222222"]') const board = page.locator('.board') const boardViewport = page.locator('.board-viewport') const containmentBand = page.locator('.folder-bands line') await expect(folder).toBeVisible() await expect(containmentBand).toHaveClass(/\bclosed\b/) const folderBefore = await boardPosition(folder) await waitForSave(page, () => dragBy(page, folder, 70, 42)) const movedFolder = await boardPosition(folder) expect(movedFolder.left).toBeGreaterThan(folderBefore.left) expect(movedFolder.top).toBeGreaterThan(folderBefore.top) await page.reload() await expect(folder).toBeVisible() expect(await boardPosition(folder)).toEqual(movedFolder) await waitForSave(page, () => folder.getByRole('button', { name: 'OPEN', exact: true }).click()) await expect(file).toHaveClass(/\bopen\b/) await expect(containmentBand).toHaveClass(/\bopen\b/) const fileBefore = await boardPosition(file) await waitForSave(page, () => dragBy(page, file, 56, 35)) const movedFile = await boardPosition(file) expect(movedFile.left).toBeGreaterThan(fileBefore.left) expect(movedFile.top).toBeGreaterThan(fileBefore.top) await page.reload() await expect(file).toHaveClass(/\bopen\b/) expect(await boardPosition(file)).toEqual(movedFile) const folderBeforePan = await boardPosition(folder) const transformBeforePan = await board.getAttribute('style') await page.getByRole('button', { name: 'HAND', exact: true }).click() const viewportBox = await boardViewport.boundingBox() if (!viewportBox) throw new Error('Board viewport is not visible') await waitForSave(page, async () => { await page.mouse.move(viewportBox.x + 240, viewportBox.y + 230) await page.mouse.down() await page.mouse.move(viewportBox.x + 290, viewportBox.y + 195, { steps: 5 }) await page.mouse.up() }) expect(await boardPosition(folder)).toEqual(folderBeforePan) const transformAfterPan = await board.getAttribute('style') expect(transformAfterPan).not.toEqual(transformBeforePan) await page.reload() await expect(folder).toBeVisible() expect(await board.getAttribute('style')).toEqual(transformAfterPan) const transformBeforePinch = await board.getAttribute('style') const browserMetricsBeforePinch = await page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio })) await page.keyboard.down('Control') await page.mouse.move(viewportBox.x + 400, viewportBox.y + 260) await page.mouse.wheel(0, -20) await page.keyboard.up('Control') await expect.poll(() => board.getAttribute('style')).not.toEqual(transformBeforePinch) expect(await page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio }))).toEqual(browserMetricsBeforePinch) })