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) })