From 8d8bc7cb1356c404260704115b12cc89591004af 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 | 104 ++++++++++
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, 1448 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 8d41f476..fb10c866 100644
--- a/components/email/email-composer.tsx
+++ b/components/email/email-composer.tsx
@@ -2050,7 +2050,7 @@ export function EmailComposer({
};
return (
-
+
@@ -2236,7 +2237,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"
>
@@ -2641,6 +2644,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 a4fb8ec8..cb8868b9 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