import { test, expect } from '@playwright/test'; import { collectConsole } from './helpers'; // Tier 1 — boot smoke (Gitea #90). Runs against the built dist/ served by // `vite preview` (see playwright.config.ts webServer). No homeserver needed. test.describe('boot', () => { test('client boots to the login screen without errors', async ({ page }) => { const consoleLog = collectConsole(page); await page.goto('/'); // The auth page is what an unauthenticated visitor lands on. await expect(page).toHaveURL(/\/login\//); await expect(page.getByLabel('Username or email')).toBeVisible(); await expect(page.getByLabel('Password', { exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: 'Login' })).toBeVisible(); // The React root rendered something (a blank #root is the classic // "bundle built but doesn't run" failure). const rootChildren = await page.locator('#root > *').count(); expect(rootChildren, '#root should have rendered children').toBeGreaterThan(0); expect(consoleLog.unexpected(), 'unexpected console/page errors during boot').toEqual([]); }); test('service worker script is served and registers', async ({ page }) => { const swResponse = await page.request.get('/sw.js'); expect(swResponse.status(), 'GET /sw.js').toBe(200); expect(swResponse.headers()['content-type'] ?? '').toMatch(/javascript/); await page.goto('/'); await expect(page.getByLabel('Username or email')).toBeVisible(); // src/index.tsx registers sw.js on load; wait for the registration to // exist (localhost counts as a secure context so this works in CI). const registered = await page.evaluate(async () => { if (!('serviceWorker' in navigator)) return 'unsupported'; const deadline = Date.now() + 15_000; while (Date.now() < deadline) { // eslint-disable-next-line no-await-in-loop const reg = await navigator.serviceWorker.getRegistration(); if (reg) return 'registered'; // eslint-disable-next-line no-await-in-loop await new Promise((r) => { setTimeout(r, 250); }); } return 'timeout'; }); expect(registered).toBe('registered'); }); test('bundled Element Call loads in a frame', async ({ page }) => { const consoleLog = collectConsole(page); // Any EC asset that fails to come back (wrong base path, missing chunk) // is the regression this test exists to catch. const failedEcRequests: string[] = []; page.on('response', (res) => { if (res.url().includes('/public/element-call/') && res.status() >= 400) { failedEcRequests.push(`${res.status()} ${res.url()}`); } }); page.on('requestfailed', (req) => { if (req.url().includes('/public/element-call/')) { failedEcRequests.push(`${req.failure()?.errorText ?? 'failed'} ${req.url()}`); } }); // Same-origin host page so the iframe is served exactly as the client // embeds it. await page.goto('/'); await expect(page.getByLabel('Username or email')).toBeVisible(); const ecResponse = await page.request.get('/public/element-call/index.html'); expect(ecResponse.status(), 'GET /public/element-call/index.html').toBe(200); await page.evaluate(() => { const frame = document.createElement('iframe'); frame.id = 'e2e-ec-frame'; frame.src = '/public/element-call/index.html'; frame.style.width = '800px'; frame.style.height = '600px'; document.body.appendChild(frame); }); const frame = page.frameLocator('#e2e-ec-frame'); // EC mounts into its own #root; rendering anything at all proves the // bundle resolved its assets from the /public/element-call/ base. await expect(frame.locator('#root > *').first()).toBeAttached({ timeout: 30_000 }); // Let EC finish its initial render/requests before inspecting the logs. await page.waitForTimeout(2_000); expect(failedEcRequests, 'Element Call asset requests that failed').toEqual([]); // Loaded bare (no widget params / no homeserver) EC runs in standalone // mode and logs a caught React error about its missing config — that is // console noise, not a broken bundle. Uncaught page errors are still // fatal, and so is anything the boot test would reject on the host page. expect(consoleLog.pageErrors, 'uncaught page errors while loading Element Call').toEqual([]); }); });