import { chromium, expect } from "@playwright/test"; export const PROFILE_DIR = process.env.DISPLAY_PROFILE_DIR || "/profiles/kiosk"; export const BASE_URL = process.env.DISPLAY_BASE_URL || "http://127.0.0.1:5199"; // A real kiosk keeps a persistent Chromium profile: the Service Worker, // Cache Storage, and IndexedDB that make offline startup possible all live // there. Incognito or an ephemeral user-data-dir would defeat the whole phase. export async function launchKiosk() { const context = await chromium.launchPersistentContext(PROFILE_DIR, { viewport: { width: 1280, height: 720 }, serviceWorkers: "allow", }); const page = context.pages()[0] || (await context.newPage()); return { context, page }; } export function diagnostics(page) { return page.evaluate(() => window.__display?.diagnostics() ?? null); } export async function waitForSlide(page, timeout = 60_000) { await expect .poll(async () => (await diagnostics(page))?.renderedAssetId ?? null, { timeout }) .not.toBeNull(); } export function opaqueLayerCount(page) { return page.evaluate( () => [...document.querySelectorAll(".scene")].filter( (layer) => Number(getComputedStyle(layer).opacity) > 0.99, ).length, ); } // Waiting for "some" cached assets is not enough: a scenario that removes // storage must first know that the specific slides it needs are present. export async function waitForCachedAssets(page, assetIds, timeout = 60_000) { const { expect } = await import("@playwright/test"); await expect .poll( async () => { const cached = await cachedMedia(page); return assetIds.every((id) => cached.some((path) => path.includes(id))); }, { timeout }, ) .toBe(true); } export async function cachedMedia(page) { return page.evaluate(async () => { if (!(await caches.has("announcements-display-assets-v1"))) return []; const cache = await caches.open("announcements-display-assets-v1"); return (await cache.keys()).map((request) => new URL(request.url).pathname); }); }