Boot tier (runs against vite preview of dist/): login screen renders with no page or console errors, sw.js is served and registers, the bundled Element Call mounts in a frame with no failed asset requests. E2EE tier (skipped without E2E_HOMESERVER/E2E_USER/E2E_PASSWORD): password login, create an encrypted room, send text, attach a compressed JPEG, and assert at the network level that every send is m.room.encrypted with no plaintext body/url/file — the regression test #6/#7/#11 lacked. Secrets and local usage documented in LOTUS_TESTING.md. Fixes #90 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
102 lines
4.4 KiB
TypeScript
102 lines
4.4 KiB
TypeScript
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([]);
|
|
});
|
|
});
|