117 lines
6.1 KiB
TypeScript
117 lines
6.1 KiB
TypeScript
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<void>) {
|
|
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)
|
|
|
|
await page.getByRole('button', { name: 'NEW EVENT', exact: true }).click()
|
|
await expect(page.getByText('Edit reconstructed event')).toBeVisible()
|
|
await page.getByLabel('Event title').fill('The browser clue was connected')
|
|
await page.getByLabel('Event narrative').fill('The investigator connected the folder to a dated source.')
|
|
await page.locator('.event-support-list input[type="checkbox"]').first().check()
|
|
await waitForSave(page, () => page.getByRole('button', { name: 'SAVE EVENT', exact: true }).click())
|
|
await page.reload()
|
|
await expect(page.locator('.evidence-card.event')).toContainText('The browser clue was connected')
|
|
await expect(page.locator('.story-strip')).toContainText('The investigator connected the folder')
|
|
await expect(page.locator('.event-support-lines line')).toHaveCount(1)
|
|
|
|
page.once('dialog', dialog => dialog.accept('Browser Template'))
|
|
const templateSaved = page.waitForResponse(candidate => candidate.request().method() === 'POST' && candidate.url().includes('/templates?edit=1') && candidate.ok())
|
|
await page.getByRole('button', { name: 'SAVE TEMPLATE', exact: true }).click()
|
|
await templateSaved
|
|
await expect(page.locator('.terminal-status')).toContainText('VERSION 1')
|
|
|
|
let promptIndex = 0
|
|
const prompts = ['browser-template', 'Browser Template Clone']
|
|
const promptHandler = (dialog: { accept(promptText?: string): Promise<void> }) => dialog.accept(prompts[promptIndex++])
|
|
page.on('dialog', promptHandler)
|
|
await page.getByRole('button', { name: 'NEW FROM TEMPLATE', exact: true }).click()
|
|
await page.waitForURL(url => url.searchParams.get('level')?.startsWith('browser-template-') === true)
|
|
page.off('dialog', promptHandler)
|
|
await expect(page.getByRole('heading', { name: 'Browser Template Clone' })).toBeVisible()
|
|
await expect(page.getByRole('button', { name: 'Reset' })).toBeEnabled()
|
|
})
|