CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m16s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 19m2s
New end-to-end coverage for two Lotus features the October audit listed as untested, checked against the server at each step: - scheduled-messages.spec: schedule (tray + one delayed event on the server), Send now (arrives once, delayed event gone), cancel (gone from the server, never sent). - offline-outbox.spec: offline, three messages show "Queued. Will send when you are back online"; back online, the server has each exactly once and in order; the queued marks clear. The scheduled-messages test found a real bug: Synapse rate-limits delayed event management per user, and a quick Send now then Cancel got a 429, so the tray showed "Could not cancel this message" although nothing was wrong. All scheduled-message calls (schedule, list, send now, cancel, restart) now wait out a 429 (Retry-After, bounded) and retry; scheduling reuses its txnId. Unit test for the retry helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import {
|
|
api,
|
|
createRoom,
|
|
enc,
|
|
ensureUser,
|
|
hsReachable,
|
|
loginUI,
|
|
openRoom,
|
|
sendText,
|
|
uniq,
|
|
} from './localHs';
|
|
|
|
// Offline outbox (#112): messages sent while offline are queued, then
|
|
// delivered once each, in order, when the connection returns. Checked on the
|
|
// server, so duplicates or reordering would show.
|
|
test.describe('offline outbox', () => {
|
|
test.beforeAll(async () => {
|
|
test.skip(!(await hsReachable()), 'local homeserver not running');
|
|
});
|
|
|
|
test('queued while offline, delivered once each and in order when back', async ({
|
|
page,
|
|
context,
|
|
}) => {
|
|
test.setTimeout(120_000);
|
|
const me = await ensureUser(uniq('outbox'));
|
|
const room = await createRoom(me, 'Outbox Room');
|
|
await sendText(me, room, 'before going offline');
|
|
await loginUI(page, me);
|
|
await openRoom(page, room);
|
|
await expect(page.getByText('before going offline')).toBeVisible();
|
|
|
|
await context.setOffline(true);
|
|
const composer = page.getByRole('textbox', { name: /message/i }).last();
|
|
const texts = [uniq('one '), uniq('two '), uniq('three ')];
|
|
for (const text of texts) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await composer.fill(text);
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await composer.press('Enter');
|
|
}
|
|
for (const text of texts) {
|
|
const item = page.locator('[data-message-item]', { hasText: text });
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await expect(item).toHaveCount(1);
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await expect(item.getByLabel(/^Queued\. Will send when you're back online/)).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
}
|
|
|
|
await context.setOffline(false);
|
|
const bodiesOnServer = async () => {
|
|
const res = await api<{ chunk: { type: string; content?: { body?: string } }[] }>(
|
|
'GET',
|
|
`/_matrix/client/v3/rooms/${enc(room)}/messages?dir=f&limit=100`,
|
|
me.token,
|
|
);
|
|
return res.chunk
|
|
.filter((e) => e.type === 'm.room.message' && texts.includes(e.content?.body ?? ''))
|
|
.map((e) => e.content?.body);
|
|
};
|
|
await expect.poll(bodiesOnServer, { timeout: 60_000, intervals: [1000] }).toEqual(texts);
|
|
for (const text of texts) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await expect(
|
|
page.locator('[data-message-item]', { hasText: text }).getByLabel(/^Queued/),
|
|
).toHaveCount(0, { timeout: 20_000 });
|
|
}
|
|
});
|
|
});
|