From b8809c2e691551f7488929ac586a7c3f6084245f Mon Sep 17 00:00:00 2001 From: Stefan Hildebrandt <695494+hildebrandttk@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:51:55 +0200 Subject: [PATCH] =?UTF-8?q?test(integration):=20dockerized=20webmail?= =?UTF-8?q?=E2=87=86Stalwart=20Playwright=20sync=20suite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an end-to-end integration harness that runs the webmail against a real Stalwart mail server in Docker and drives it with Playwright, focused on the mail/folder synchronisation behaviour (unread/total counters, folder-list sync, account-scoped Unified Mailbox) that the unified-mailbox work touches. - integration/ stack: Stalwart (declarative bootstrap: alice/bob/carol, submission + IMAP listeners, permissive CORS) + webmail (dev mode, so the browser's plaintext cross-origin JMAP calls aren't blocked by the prod CSP). - Helpers: dependency-free SMTP submit client, JMAP client for seeding / inspecting server state, and page helpers (login, add/switch account, locale-independent folder-counter reads). - Specs: login, single-account sync (receive/read/move/delete/folder-create/ burst) and multi-account (per-account isolation + cross-account Unified Inbox aggregation + background-account delivery). 12 tests, all green. - Add focused data-testid hooks to the mail UI (folder rows + counters, email list items, account switcher, composer) for stable selectors. - Exclude examples/ and integration/ from tsconfig/eslint/.dockerignore. --- .dockerignore | 5 + components/email/email-composer.tsx | 8 +- components/email/thread-list-item.tsx | 4 + components/layout/account-switcher.tsx | 6 + components/layout/sidebar.tsx | 28 ++- eslint.config.mjs | 2 + integration/.env.example | 11 ++ integration/.gitignore | 10 + integration/README.md | 123 ++++++++++++ integration/docker-compose.yml | 78 ++++++++ integration/run-tests.sh | 36 ++++ integration/stalwart/.dockerignore | 2 + integration/stalwart/Dockerfile | 29 +++ integration/stalwart/entrypoint.sh | 129 +++++++++++++ integration/stalwart/plan-accounts.ndjson.tpl | 8 + integration/stalwart/plan-bootstrap.ndjson | 1 + integration/stalwart/prepare-stalwart-cli.sh | 48 +++++ integration/tests/01-login.spec.ts | 37 ++++ integration/tests/02-mail-sync.spec.ts | 125 ++++++++++++ integration/tests/03-multi-account.spec.ts | 99 ++++++++++ integration/tests/global-setup.ts | 64 +++++++ integration/tests/global-teardown.ts | 23 +++ integration/tests/helpers/app.ts | 180 ++++++++++++++++++ integration/tests/helpers/config.ts | 45 +++++ integration/tests/helpers/jmap.ts | 142 ++++++++++++++ integration/tests/helpers/smtp.ts | 128 +++++++++++++ integration/webmail-config/policy.json | 7 + integration/webmail.Dockerfile | 33 ++++ package.json | 1 + playwright.integration.config.ts | 32 ++++ tsconfig.json | 4 +- 31 files changed, 1443 insertions(+), 5 deletions(-) create mode 100644 integration/.env.example create mode 100644 integration/.gitignore create mode 100644 integration/README.md create mode 100644 integration/docker-compose.yml create mode 100755 integration/run-tests.sh create mode 100644 integration/stalwart/.dockerignore create mode 100644 integration/stalwart/Dockerfile create mode 100755 integration/stalwart/entrypoint.sh create mode 100644 integration/stalwart/plan-accounts.ndjson.tpl create mode 100644 integration/stalwart/plan-bootstrap.ndjson create mode 100755 integration/stalwart/prepare-stalwart-cli.sh create mode 100644 integration/tests/01-login.spec.ts create mode 100644 integration/tests/02-mail-sync.spec.ts create mode 100644 integration/tests/03-multi-account.spec.ts create mode 100644 integration/tests/global-setup.ts create mode 100644 integration/tests/global-teardown.ts create mode 100644 integration/tests/helpers/app.ts create mode 100644 integration/tests/helpers/config.ts create mode 100644 integration/tests/helpers/jmap.ts create mode 100644 integration/tests/helpers/smtp.ts create mode 100644 integration/webmail-config/policy.json create mode 100644 integration/webmail.Dockerfile create mode 100644 playwright.integration.config.ts diff --git a/.dockerignore b/.dockerignore index 771c8d7d..509fe74a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,8 @@ scripts/ TODO.md *.md !README.md +# Sibling projects / test harness - not part of the webmail image +examples/ +integration/ +e2e/ +**/node_modules diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 4e022d29..bc6f873f 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -2041,7 +2041,7 @@ export function EmailComposer({ }; return ( -
+
@@ -2227,7 +2228,7 @@ export function EmailComposer({
{/* To field */} -
+
{t('to')}: {t('subject_label')} handleSend()} disabled={!canSend || isSending} title={getSendTooltip()} + data-testid="composer-send" className="rounded-e-none border-e border-primary-foreground/20" > @@ -2632,6 +2635,7 @@ export function EmailComposer({ onClick={() => handleSend()} disabled={!canSend || isSending} title={getSendTooltip()} + data-testid="composer-send" className="hidden md:inline-flex" > diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index 7b60bd80..e93e37f4 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -166,6 +166,10 @@ const SingleEmailItem = React.forwardRef( ref={ref} {...dragHandlers} {...longPressHandlers} + data-testid="email-list-item" + data-email-id={email.id} + data-subject={email.subject || ''} + data-unread={isUnread ? 'true' : 'false'} className={cn( "relative group cursor-pointer select-none transition-shadow duration-200 border-b border-border overflow-hidden", resolvedColorTag ? resolvedColorTag : ( diff --git a/components/layout/account-switcher.tsx b/components/layout/account-switcher.tsx index 8264c991..179aeddd 100644 --- a/components/layout/account-switcher.tsx +++ b/components/layout/account-switcher.tsx @@ -151,6 +151,8 @@ export function AccountSwitcher({ variant = "rail", className }: AccountSwitcher