Wait for the actual Back event across viewer instances, cancel queued entries when their owner closes, and remove the 250ms assumption. Restore scoped content-size resets so legacy global lightbox CSS cannot narrow the landscape panel. Three delayed-history unit regressions pass, including a ten-second delay. Five Chromium browser checks pass at 360x800, 390x844, touch landscape 844x390 and desktop 1440x900, plus real local video playback/native fullscreen with source and position retained. Screenshots inspected. Fixture server uses explicit cwd and no filesystem watching; no node proxy. Strict integrated project types and physical Companion UAT remain separate.
82 lines
5.0 KiB
TypeScript
82 lines
5.0 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route('**/*', route => (new URL(route.request().url()).hostname === '127.0.0.1' || route.request().url().startsWith('data:')) ? route.continue() : route.abort())
|
|
})
|
|
|
|
for (const viewport of [{ width: 360, height: 800 }, { width: 390, height: 844 }, { width: 844, height: 390 }, { width: 1440, height: 900 }]) {
|
|
test.describe(`${viewport.width}x${viewport.height}`, () => {
|
|
test.use({ viewport, hasTouch: viewport.width <= 844 })
|
|
test(`modal layout, focus, fullscreen fallback and Back at ${viewport.width}x${viewport.height}`, async ({ page }) => {
|
|
await page.setViewportSize(viewport)
|
|
await page.goto('/e2e/fixtures/viewer.html')
|
|
// Exercise the embedded-WebView denial branch without requiring native OS fullscreen.
|
|
await page.evaluate(() => { HTMLElement.prototype.requestFullscreen = () => Promise.reject(new Error('Embedded browser denied fullscreen')) })
|
|
await page.locator('#open-image').click()
|
|
const dialog = page.getByRole('dialog', { name: 'File viewer', exact: true })
|
|
await expect(dialog).toBeVisible()
|
|
const close = page.getByRole('button', { name: 'Close viewer', exact: true })
|
|
await expect(close).toBeFocused()
|
|
const panel = await dialog.locator('.lightbox-panel').boundingBox()
|
|
const content = await dialog.locator('.lightbox-content').boundingBox()
|
|
expect(Math.abs(panel!.x + panel!.width / 2 - content!.x - content!.width / 2)).toBeLessThan(2)
|
|
for (const label of ['Close viewer', 'Fullscreen', 'Previous file', 'Next file']) {
|
|
const box = await page.getByRole('button', { name: label, exact: true }).boundingBox()
|
|
expect(box).not.toBeNull()
|
|
expect(box!.width).toBeGreaterThanOrEqual(44)
|
|
expect(box!.height).toBeGreaterThanOrEqual(44)
|
|
expect(box!.x).toBeGreaterThanOrEqual(0)
|
|
expect(box!.y).toBeGreaterThanOrEqual(24)
|
|
expect(box!.x + box!.width).toBeLessThanOrEqual(viewport.width)
|
|
expect(box!.y + box!.height).toBeLessThanOrEqual(viewport.height - 20)
|
|
}
|
|
await page.keyboard.press('Shift+Tab')
|
|
expect(await dialog.evaluate(el => el.contains(document.activeElement))).toBe(true)
|
|
expect(await page.locator('#app').evaluate(el => el.inert)).toBe(true)
|
|
await page.getByRole('button', { name: 'Fullscreen', exact: true }).click()
|
|
await expect(dialog).toHaveClass(/lightbox-expanded/)
|
|
await page.keyboard.press('Escape')
|
|
await expect(dialog).not.toHaveClass(/lightbox-expanded/)
|
|
await page.screenshot({ path: `${process.env.VIEWER_SCREENSHOTS ?? '/tmp'}/viewer-${viewport.width}x${viewport.height}.png` })
|
|
await page.goBack()
|
|
await expect(dialog).toHaveCount(0)
|
|
await expect(page.locator('#open-image')).toBeFocused()
|
|
expect(await page.locator('#app').evaluate(el => el.inert)).toBe(false)
|
|
// Reopening/explicit close does not navigate away or consume a later viewer's entry.
|
|
await page.locator('#open-document').click()
|
|
await expect(dialog).toContainText('Invoice with a long filename.pdf')
|
|
await expect(dialog.locator('iframe, embed, object')).toHaveCount(0)
|
|
await dialog.getByRole('button', { name: 'Download', exact: true }).click()
|
|
await expect(page.locator('#download-result')).toHaveText('/document')
|
|
await close.click()
|
|
await expect(dialog).toHaveCount(0)
|
|
await page.locator('#open-image').click()
|
|
await page.goBack()
|
|
await expect(dialog).toHaveCount(0)
|
|
expect(page.url()).toContain('/e2e/fixtures/viewer.html')
|
|
})
|
|
})
|
|
}
|
|
|
|
test('real video playback retains controls, source and position through fullscreen changes', async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await page.goto('/e2e/fixtures/viewer.html')
|
|
await page.locator('#open-video').click()
|
|
const video = page.locator('.lightbox-media-video')
|
|
await video.evaluate(async (node: HTMLVideoElement) => { node.muted = true; await node.play() })
|
|
await expect.poll(() => video.evaluate((node: HTMLVideoElement) => node.currentTime)).toBeGreaterThan(0)
|
|
const initial = await video.evaluate((node: HTMLVideoElement) => ({ src: node.currentSrc, time: node.currentTime }))
|
|
expect(await video.getAttribute('playsinline')).not.toBeNull()
|
|
const controls = await video.boundingBox()
|
|
const fullscreen = await page.getByRole('button', { name: 'Fullscreen', exact: true }).boundingBox()
|
|
expect(controls!.y + controls!.height).toBeLessThanOrEqual(fullscreen!.y)
|
|
await page.getByRole('button', { name: 'Fullscreen', exact: true }).click()
|
|
await expect(page.getByRole('button', { name: 'Exit fullscreen', exact: true })).toBeVisible()
|
|
await page.getByRole('button', { name: 'Exit fullscreen', exact: true }).click()
|
|
await expect(page.getByRole('button', { name: 'Fullscreen', exact: true })).toBeVisible()
|
|
expect(await video.evaluate((node: HTMLVideoElement) => node.currentSrc)).toBe(initial.src)
|
|
expect(await video.evaluate((node: HTMLVideoElement) => node.currentTime)).toBeGreaterThanOrEqual(initial.time)
|
|
await page.getByRole('button', { name: 'Close viewer', exact: true }).click()
|
|
await expect(video).toHaveCount(0)
|
|
})
|