CI / Build & Quality Checks (push) Successful in 1m29s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 8s
CI / Trigger Desktop Build (push) Successful in 5s
CI / Playwright smoke (e2e) (push) Successful in 4m17s
The e2e job now runs scripts/dev-homeserver.sh start + dev-seed.py before Playwright (installing python3-venv if the runner lacks it) and stops it afterwards. e2e/local-homeserver.spec.ts registers its own users and rooms over the CS API and drives the built client — no prod secrets — covering the fixes that were reproduced with scratch scripts this week: login/send/receive, own-message scroll (#212), /kick toast (#216), upload 413 wording (#213), forward provenance, thread panel at 1400px (#218), timeline lightbox (#219), clock-skew banner via page.clock (#158), status save under the presence limit (#226), long-press action sheet on a Pixel 7 emulation (#166). Skips itself when no homeserver answers, so > lotus-chat@4.12.7-lotus test:e2e > playwright test Running 16 tests using 1 worker ✓ 1 [chromium] › e2e/boot.spec.ts:8:3 › boot › client boots to the login screen without errors (1.5s) ✓ 2 [chromium] › e2e/boot.spec.ts:27:3 › boot › service worker script is served and registers (1.4s) ✓ 3 [chromium] › e2e/boot.spec.ts:54:3 › boot › bundled Element Call loads in a frame (3.8s) - 4 [chromium] › e2e/e2ee-composer.spec.ts:67:3 › E2EE composer › logs in with a password and reaches the client - 5 [chromium] › e2e/e2ee-composer.spec.ts:84:3 › E2EE composer › creates a private encrypted room and sends a text message - 6 [chromium] › e2e/e2ee-composer.spec.ts:146:3 › E2EE composer › attaches a compressed image and it is sent encrypted ✓ 7 [chromium] › e2e/local-homeserver.spec.ts:31:3 › local homeserver regression › logs in, opens a room, sends and receives (4.5s) ✓ 8 [chromium] › e2e/local-homeserver.spec.ts:58:3 › local homeserver regression › your own message scrolls into view even after scrolling up (#212) (11.4s) ✓ 9 [chromium] › e2e/local-homeserver.spec.ts:76:3 › local homeserver regression › /kick failure is reported, not swallowed (#216) (5.4s) ✓ 10 [chromium] › e2e/local-homeserver.spec.ts:90:3 › local homeserver regression › upload failure shows a plain sentence, never the raw MatrixError (#213) (3.7s) ✓ 11 [chromium] › e2e/local-homeserver.spec.ts:122:3 › local homeserver regression › forwarded message carries its provenance header (6.7s) ✓ 12 [chromium] › e2e/local-homeserver.spec.ts:143:3 › local homeserver regression › thread panel: opens from the chip and yields the member drawer at 1400px (#218) (4.7s) ✓ 13 [chromium] › e2e/local-homeserver.spec.ts:172:3 › local homeserver regression › timeline image opens the gallery lightbox (#219) (3.9s) ✓ 14 [chromium] › e2e/local-homeserver.spec.ts:204:3 › local homeserver regression › warns when the local clock is far off the server (#158) (6.5s) ✓ 15 [chromium] › e2e/local-homeserver.spec.ts:221:3 › local homeserver regression › status save survives the presence rate limit (#226) (12.6s) ✓ 16 [chromium] › e2e/local-homeserver.spec.ts:265:3 › local homeserver regression › touch: long-press opens the message action sheet (#166) (5.0s) 3 skipped 13 passed (1.2m) still works cold. 13 pass locally against dist + the dev homeserver in 1.8 min. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { Page, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Tier 3 (Gitea #220): regression tests against a throwaway Synapse the job
|
|
* starts itself (`scripts/dev-homeserver.sh start`). No prod secrets. Set
|
|
* E2E_LOCAL_HS (default http://localhost:8008) — the spec skips when the
|
|
* server isn't reachable.
|
|
*/
|
|
export const HS = process.env.E2E_LOCAL_HS || 'http://localhost:8008';
|
|
export const PASSWORD = 'password123';
|
|
export const enc = encodeURIComponent;
|
|
|
|
export async function hsReachable(): Promise<boolean> {
|
|
try {
|
|
const res = await fetch(`${HS}/_matrix/client/versions`);
|
|
return res.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function api<T = Record<string, unknown>>(
|
|
method: string,
|
|
path: string,
|
|
token?: string,
|
|
body?: unknown,
|
|
): Promise<T> {
|
|
const res = await fetch(`${HS}${path}`, {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
},
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
export type TestUser = { localpart: string; userId: string; token: string };
|
|
|
|
/** Registers (open registration) or logs in a test user. */
|
|
export async function ensureUser(localpart: string): Promise<TestUser> {
|
|
const reg = await api<{ user_id?: string; access_token?: string }>(
|
|
'POST',
|
|
'/_matrix/client/v3/register',
|
|
undefined,
|
|
{ username: localpart, password: PASSWORD, auth: { type: 'm.login.dummy' } },
|
|
);
|
|
if (reg.access_token && reg.user_id) {
|
|
return { localpart, userId: reg.user_id, token: reg.access_token };
|
|
}
|
|
const login = await api<{ user_id: string; access_token: string }>(
|
|
'POST',
|
|
'/_matrix/client/v3/login',
|
|
undefined,
|
|
{
|
|
type: 'm.login.password',
|
|
identifier: { type: 'm.id.user', user: localpart },
|
|
password: PASSWORD,
|
|
},
|
|
);
|
|
return { localpart, userId: login.user_id, token: login.access_token };
|
|
}
|
|
|
|
export async function createRoom(
|
|
owner: TestUser,
|
|
name: string,
|
|
opts: { invite?: string[]; preset?: string } = {},
|
|
): Promise<string> {
|
|
const res = await api<{ room_id: string }>('POST', '/_matrix/client/v3/createRoom', owner.token, {
|
|
name,
|
|
preset: opts.preset ?? 'public_chat',
|
|
invite: opts.invite,
|
|
});
|
|
return res.room_id;
|
|
}
|
|
|
|
export async function joinRoom(user: TestUser, roomId: string): Promise<void> {
|
|
await api('POST', `/_matrix/client/v3/rooms/${enc(roomId)}/join`, user.token, {});
|
|
}
|
|
|
|
let txn = 0;
|
|
export async function sendText(
|
|
user: TestUser,
|
|
roomId: string,
|
|
body: string,
|
|
extra: Record<string, unknown> = {},
|
|
): Promise<string> {
|
|
txn += 1;
|
|
const res = await api<{ event_id: string }>(
|
|
'PUT',
|
|
`/_matrix/client/v3/rooms/${enc(roomId)}/send/m.room.message/e2e${Date.now()}_${txn}`,
|
|
user.token,
|
|
{ msgtype: 'm.text', body, ...extra },
|
|
);
|
|
return res.event_id;
|
|
}
|
|
|
|
/** Password login through the UI against the local homeserver. */
|
|
export async function loginUI(page: Page, user: TestUser): Promise<void> {
|
|
await page.goto(`/login/${enc(HS)}/`);
|
|
await page.getByLabel('Username or email').fill(user.localpart);
|
|
await page.getByLabel('Password', { exact: true }).fill(PASSWORD);
|
|
await page.getByRole('button', { name: 'Login' }).click();
|
|
await page.waitForURL(/\/home/, { timeout: 60_000 });
|
|
}
|
|
|
|
export async function openRoom(page: Page, roomId: string): Promise<void> {
|
|
await page.goto(`/home/${enc(roomId)}`);
|
|
await expect(page.locator('[data-slate-editor]').first()).toBeVisible({ timeout: 30_000 });
|
|
}
|
|
|
|
export const uniq = (prefix: string): string =>
|
|
`${prefix}${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|