test(e2e): Playwright smoke test — boot tier always, E2EE composer tier with credentials
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
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
// Console noise that is expected on a clean boot and must not fail the smoke
|
||||
// test. Keep this list short and specific — every entry should name a known,
|
||||
// understood source.
|
||||
const BENIGN_CONSOLE_PATTERNS: RegExp[] = [
|
||||
// README: after login you may see a 404 for a missing avatar thumbnail —
|
||||
// "not a login failure". Also covers the generic resource-404 console line.
|
||||
/_matrix\/(client|media)\/v\d+\/(media\/)?thumbnail/i,
|
||||
/Failed to load resource: the server responded with a status of 404/i,
|
||||
// The login page probes `POST /_matrix/client/v3/register` to learn whether
|
||||
// registration is open; the homeserver answers 401 + UIA flows by design.
|
||||
/Failed to load resource: the server responded with a status of 401/i,
|
||||
// Homeserver discovery pings can fail on a runner with no outbound network.
|
||||
/\/\.well-known\/matrix\/client/i,
|
||||
/Failed to fetch|NetworkError|ERR_NAME_NOT_RESOLVED|ERR_INTERNET_DISCONNECTED/i,
|
||||
// React devtools hint in production bundles.
|
||||
/Download the React DevTools/i,
|
||||
];
|
||||
|
||||
export type ConsoleCollector = {
|
||||
errors: string[];
|
||||
pageErrors: string[];
|
||||
/** Errors not matched by the benign allowlist. */
|
||||
unexpected: () => string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Records console.error lines and uncaught page errors for the given page.
|
||||
* Attach BEFORE navigating so nothing emitted during boot is missed.
|
||||
*/
|
||||
export function collectConsole(page: Page): ConsoleCollector {
|
||||
const errors: string[] = [];
|
||||
const pageErrors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
page.on('pageerror', (err) => {
|
||||
pageErrors.push(err.message);
|
||||
});
|
||||
return {
|
||||
errors,
|
||||
pageErrors,
|
||||
unexpected: () => [
|
||||
...pageErrors.map((m) => `pageerror: ${m}`),
|
||||
...errors.filter((m) => !BENIGN_CONSOLE_PATTERNS.some((re) => re.test(m))),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a small JPEG in the browser (canvas.toBlob) and returns its bytes.
|
||||
* JPEG rather than PNG so the composer's "Compress image" path actually
|
||||
* re-encodes (compressImage() deliberately skips PNG to preserve alpha).
|
||||
*/
|
||||
export async function generateJpeg(page: Page, size = 96): Promise<Buffer> {
|
||||
const dataUrl = await page.evaluate((px) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = px;
|
||||
canvas.height = px;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('canvas 2d context unavailable');
|
||||
const grad = ctx.createLinearGradient(0, 0, px, px);
|
||||
grad.addColorStop(0, '#7c3aed');
|
||||
grad.addColorStop(1, '#f59e0b');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, px, px);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = `${Math.floor(px / 4)}px sans-serif`;
|
||||
ctx.fillText('e2e', px / 8, px / 2);
|
||||
return canvas.toDataURL('image/jpeg', 0.95);
|
||||
}, size);
|
||||
return Buffer.from(dataUrl.split(',')[1], 'base64');
|
||||
}
|
||||
Reference in New Issue
Block a user