mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
test fixes
This commit is contained in:
@@ -385,6 +385,7 @@ down-e2e: stop-e2e ## alias for stop-e2e
|
||||
demo-e2e: ## Populate the e2e database with demo data
|
||||
@echo "$(BLUE)\n\n| 📝 Bootstrapping E2E demo data... \n$(RESET)"
|
||||
@$(COMPOSE_E2E) run --rm backend python manage.py e2e_demo
|
||||
@$(COMPOSE_E2E) run --rm backend python manage.py e2e_clientbridge
|
||||
.PHONY: demo-e2e
|
||||
|
||||
start-e2e: ## Start e2e services (migrate, seed, etc.)
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Django management command to bootstrap client-bridge E2E test data.
|
||||
|
||||
Separated from e2e_demo so that db:reset (used by non-client-bridge tests)
|
||||
doesn't pay the cost of creating channels and EML blobs on every call.
|
||||
"""
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import format_datetime
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from core import models
|
||||
from core.enums import MailboxRoleChoices, ThreadAccessRoleChoices
|
||||
|
||||
BROWSERS = ["chromium", "firefox", "webkit"]
|
||||
DOMAIN_NAME = "example.local"
|
||||
SHARED_MAILBOX_LOCAL_PART = "shared.e2e"
|
||||
CLIENTBRIDGE_APP_PASSWORD = "e2e-client-bridge-password" # noqa: S105
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Create client-bridge channels and IMAP test data for E2E testing."""
|
||||
|
||||
help = "Create client-bridge E2E data (channels and IMAP test messages)"
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
"""Execute the command."""
|
||||
self.stdout.write(
|
||||
self.style.WARNING("\n\n| Creating client-bridge E2E data\n")
|
||||
)
|
||||
|
||||
domain = models.MailDomain.objects.get(name=DOMAIN_NAME)
|
||||
|
||||
# Create channels for all regular user mailboxes
|
||||
for browser in BROWSERS:
|
||||
try:
|
||||
mailbox = models.Mailbox.objects.get(
|
||||
local_part=f"user.e2e.{browser}", domain=domain
|
||||
)
|
||||
self._create_clientbridge_channel(mailbox)
|
||||
except models.Mailbox.DoesNotExist:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f" Mailbox user.e2e.{browser} not found, skipping"
|
||||
)
|
||||
)
|
||||
|
||||
# Create channel for shared mailbox
|
||||
try:
|
||||
shared_mailbox = models.Mailbox.objects.get(
|
||||
local_part=SHARED_MAILBOX_LOCAL_PART, domain=domain
|
||||
)
|
||||
self._create_clientbridge_channel(shared_mailbox)
|
||||
except models.Mailbox.DoesNotExist:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(" Shared mailbox not found, skipping")
|
||||
)
|
||||
|
||||
# Create IMAP test messages on the first regular user's mailbox (chromium)
|
||||
first_mailbox = models.Mailbox.objects.get(
|
||||
local_part="user.e2e.chromium", domain=domain
|
||||
)
|
||||
self._create_imap_test_messages(first_mailbox, domain)
|
||||
|
||||
def _create_clientbridge_channel(self, mailbox):
|
||||
"""Create a client-bridge channel with a known password for e2e testing."""
|
||||
access = models.MailboxAccess.objects.filter(
|
||||
mailbox=mailbox, role=MailboxRoleChoices.ADMIN
|
||||
).first()
|
||||
if not access:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f" No admin user found for {mailbox}, skipping channel"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
_channel, created = models.Channel.objects.get_or_create(
|
||||
mailbox=mailbox,
|
||||
type="client-bridge",
|
||||
defaults={
|
||||
"name": f"E2E client-bridge ({mailbox})",
|
||||
"user": access.user,
|
||||
"settings": {"role": "sender"},
|
||||
"encrypted_settings": {"password": CLIENTBRIDGE_APP_PASSWORD},
|
||||
},
|
||||
)
|
||||
if created:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" Created client-bridge channel for {mailbox}")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f" Client-bridge channel already exists for {mailbox}"
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _make_eml(subject, sender_email, recipient_email, body, sent_at):
|
||||
"""Build a minimal RFC 5322 message and return raw bytes."""
|
||||
msg = MIMEText(body, "plain")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = recipient_email
|
||||
msg["Date"] = format_datetime(sent_at)
|
||||
return msg.as_bytes()
|
||||
|
||||
def _create_imap_test_messages(self, mailbox, domain):
|
||||
"""Create messages for IMAP read/unread e2e testing."""
|
||||
sender_email = f"imap-sender@{domain.name}"
|
||||
recipient_email = str(mailbox)
|
||||
sender_contact, _ = models.Contact.objects.get_or_create(
|
||||
email=sender_email,
|
||||
mailbox=mailbox,
|
||||
defaults={"name": "IMAP Test Sender"},
|
||||
)
|
||||
|
||||
# Thread 1: an unread message
|
||||
now = timezone.now()
|
||||
thread1 = models.Thread.objects.create(subject="IMAP unread test")
|
||||
models.ThreadAccess.objects.create(
|
||||
thread=thread1,
|
||||
mailbox=mailbox,
|
||||
role=ThreadAccessRoleChoices.EDITOR,
|
||||
read_at=None,
|
||||
)
|
||||
eml1 = self._make_eml(
|
||||
"IMAP unread test",
|
||||
sender_email,
|
||||
recipient_email,
|
||||
"This message should appear as unread in IMAP.",
|
||||
now,
|
||||
)
|
||||
blob1 = mailbox.create_blob(content=eml1, content_type="message/rfc822")
|
||||
models.Message.objects.create(
|
||||
thread=thread1,
|
||||
sender=sender_contact,
|
||||
subject="IMAP unread test",
|
||||
is_sender=False,
|
||||
is_draft=False,
|
||||
sent_at=now,
|
||||
blob=blob1,
|
||||
)
|
||||
thread1.update_stats()
|
||||
|
||||
# Thread 2: a read message
|
||||
sent_at2 = now - timezone.timedelta(minutes=5)
|
||||
thread2 = models.Thread.objects.create(subject="IMAP read test")
|
||||
models.ThreadAccess.objects.create(
|
||||
thread=thread2,
|
||||
mailbox=mailbox,
|
||||
role=ThreadAccessRoleChoices.EDITOR,
|
||||
read_at=now + timezone.timedelta(minutes=1),
|
||||
)
|
||||
eml2 = self._make_eml(
|
||||
"IMAP read test",
|
||||
sender_email,
|
||||
recipient_email,
|
||||
"This message should appear as read in IMAP.",
|
||||
sent_at2,
|
||||
)
|
||||
blob2 = mailbox.create_blob(content=eml2, content_type="message/rfc822")
|
||||
models.Message.objects.create(
|
||||
thread=thread2,
|
||||
sender=sender_contact,
|
||||
subject="IMAP read test",
|
||||
is_sender=False,
|
||||
is_draft=False,
|
||||
sent_at=sent_at2,
|
||||
blob=blob2,
|
||||
)
|
||||
thread2.update_stats()
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" Created IMAP test messages for {mailbox}")
|
||||
)
|
||||
@@ -3,12 +3,11 @@ Django management command to bootstrap E2E demo data.
|
||||
|
||||
This command creates demo users, mailboxes, shared mailboxes, and outbox test data
|
||||
for E2E testing across different browsers (chromium, firefox, webkit).
|
||||
|
||||
Client-bridge specific data (channels, IMAP test messages) is handled by the
|
||||
separate e2e_clientbridge command to avoid recreating it on every db:reset.
|
||||
"""
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import format_datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
@@ -25,7 +24,6 @@ from core.services.identity.keycloak import get_keycloak_admin_client
|
||||
BROWSERS = ["chromium", "firefox", "webkit"]
|
||||
DOMAIN_NAME = "example.local"
|
||||
SHARED_MAILBOX_LOCAL_PART = "shared.e2e"
|
||||
CLIENTBRIDGE_APP_PASSWORD = "e2e-client-bridge-password" # noqa: S105
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -127,18 +125,6 @@ class Command(BaseCommand):
|
||||
for browser in BROWSERS:
|
||||
self._create_outbox_test_data(domain, browser)
|
||||
|
||||
# Step 7: Create client-bridge channels and IMAP test data
|
||||
if settings.FEATURE_CLIENTBRIDGE:
|
||||
self.stdout.write(
|
||||
"\n-- 6/6 📧 Creating client-bridge channels and IMAP test data"
|
||||
)
|
||||
for _user, mailbox in regular_users:
|
||||
self._create_clientbridge_channel(mailbox)
|
||||
self._create_clientbridge_channel(shared_mailbox)
|
||||
# Create IMAP test messages on the first regular user's mailbox
|
||||
_first_user, first_mailbox = regular_users[0]
|
||||
self._create_imap_test_messages(first_mailbox, domain)
|
||||
|
||||
def _create_user_with_mailbox(
|
||||
self, email, domain, is_domain_admin=False, is_superuser=False
|
||||
):
|
||||
@@ -405,122 +391,3 @@ class Command(BaseCommand):
|
||||
|
||||
return thread
|
||||
|
||||
def _create_clientbridge_channel(self, mailbox):
|
||||
"""Create a client-bridge channel with a known password for e2e testing."""
|
||||
# Use the first user with admin access to this mailbox
|
||||
access = models.MailboxAccess.objects.filter(
|
||||
mailbox=mailbox, role=MailboxRoleChoices.ADMIN
|
||||
).first()
|
||||
if not access:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f" No admin user found for {mailbox}, skipping channel"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
_channel, created = models.Channel.objects.get_or_create(
|
||||
mailbox=mailbox,
|
||||
type="client-bridge",
|
||||
defaults={
|
||||
"name": f"E2E client-bridge ({mailbox})",
|
||||
"user": access.user,
|
||||
"settings": {"role": "sender"},
|
||||
"encrypted_settings": {"password": CLIENTBRIDGE_APP_PASSWORD},
|
||||
},
|
||||
)
|
||||
if created:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" Created client-bridge channel for {mailbox}")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f" Client-bridge channel already exists for {mailbox}"
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _make_eml(subject, sender_email, recipient_email, body, sent_at):
|
||||
"""Build a minimal RFC 5322 message and return raw bytes."""
|
||||
msg = MIMEText(body, "plain")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = recipient_email
|
||||
msg["Date"] = format_datetime(sent_at)
|
||||
return msg.as_bytes()
|
||||
|
||||
def _create_imap_test_messages(self, mailbox, domain):
|
||||
"""Create messages for IMAP read/unread e2e testing.
|
||||
|
||||
Creates two threads with proper EML blobs so the client-bridge
|
||||
can serve envelope data (subject, from, date) over IMAP.
|
||||
"""
|
||||
sender_email = f"imap-sender@{domain.name}"
|
||||
recipient_email = str(mailbox)
|
||||
sender_contact, _ = models.Contact.objects.get_or_create(
|
||||
email=sender_email,
|
||||
mailbox=mailbox,
|
||||
defaults={"name": "IMAP Test Sender"},
|
||||
)
|
||||
|
||||
# Thread 1: an unread message
|
||||
now = timezone.now()
|
||||
thread1 = models.Thread.objects.create(subject="IMAP unread test")
|
||||
models.ThreadAccess.objects.create(
|
||||
thread=thread1,
|
||||
mailbox=mailbox,
|
||||
role=ThreadAccessRoleChoices.EDITOR,
|
||||
read_at=None, # No read_at → all messages unread
|
||||
)
|
||||
eml1 = self._make_eml(
|
||||
"IMAP unread test",
|
||||
sender_email,
|
||||
recipient_email,
|
||||
"This message should appear as unread in IMAP.",
|
||||
now,
|
||||
)
|
||||
blob1 = mailbox.create_blob(content=eml1, content_type="message/rfc822")
|
||||
models.Message.objects.create(
|
||||
thread=thread1,
|
||||
sender=sender_contact,
|
||||
subject="IMAP unread test",
|
||||
is_sender=False,
|
||||
is_draft=False,
|
||||
sent_at=now,
|
||||
blob=blob1,
|
||||
)
|
||||
thread1.update_stats()
|
||||
|
||||
# Thread 2: a read message
|
||||
sent_at2 = now - timezone.timedelta(minutes=5)
|
||||
thread2 = models.Thread.objects.create(subject="IMAP read test")
|
||||
models.ThreadAccess.objects.create(
|
||||
thread=thread2,
|
||||
mailbox=mailbox,
|
||||
role=ThreadAccessRoleChoices.EDITOR,
|
||||
read_at=now
|
||||
+ timezone.timedelta(minutes=1), # read_at after message created_at → read
|
||||
)
|
||||
eml2 = self._make_eml(
|
||||
"IMAP read test",
|
||||
sender_email,
|
||||
recipient_email,
|
||||
"This message should appear as read in IMAP.",
|
||||
sent_at2,
|
||||
)
|
||||
blob2 = mailbox.create_blob(content=eml2, content_type="message/rfc822")
|
||||
models.Message.objects.create(
|
||||
thread=thread2,
|
||||
sender=sender_contact,
|
||||
subject="IMAP read test",
|
||||
is_sender=False,
|
||||
is_draft=False,
|
||||
sent_at=sent_at2,
|
||||
blob=blob2,
|
||||
)
|
||||
thread2.update_stats()
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" Created IMAP test messages for {mailbox}")
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"django": "./bin/backend-manage.sh",
|
||||
"db:flush": "npm run django flush -- --no-input",
|
||||
"db:bootstrap": "npm run django e2e_demo",
|
||||
"db:bootstrap-clientbridge": "npm run django e2e_clientbridge",
|
||||
"db:reset": "npm run db:flush && npm run db:bootstrap"
|
||||
},
|
||||
"keywords": ["e2e", "playwright", "testing"],
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
CLIENTBRIDGE_APP_PASSWORD,
|
||||
API_URL,
|
||||
} from "../constants";
|
||||
import { getMailboxEmail } from "../utils";
|
||||
import { bootstrapClientBridge } from "../utils";
|
||||
import { signInKeycloakIfNeeded } from "../utils-test";
|
||||
|
||||
const IMAP_USER = "user.e2e.chromium@example.local";
|
||||
@@ -35,6 +35,10 @@ async function createImapClient(): Promise<ImapFlow> {
|
||||
}
|
||||
|
||||
test.describe("Client Bridge IMAP", () => {
|
||||
test.beforeAll(async () => {
|
||||
await bootstrapClientBridge();
|
||||
});
|
||||
|
||||
test("should authenticate and list INBOX", async () => {
|
||||
const client = await createImapClient();
|
||||
try {
|
||||
@@ -97,7 +101,7 @@ test.describe("Client Bridge IMAP", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("should sync read state from IMAP to webmail API", async ({ page, browserName }) => {
|
||||
test("should sync read state from IMAP to webmail API", async ({ page }) => {
|
||||
// Sign in to get an authenticated session for API calls
|
||||
await signInKeycloakIfNeeded({ page, username: `user.e2e.chromium` });
|
||||
|
||||
@@ -126,24 +130,16 @@ test.describe("Client Bridge IMAP", () => {
|
||||
}
|
||||
|
||||
// Verify via the API that the thread is now read
|
||||
// The API returns is_unread on messages when mailbox_id is provided
|
||||
const mailboxEmail = getMailboxEmail("user", "chromium");
|
||||
|
||||
// Get threads to find the one with "IMAP unread test"
|
||||
const threadsResp = await page.request.get(`${API_URL}/api/v1.0/threads/`, {
|
||||
params: { mailbox_id: "" }, // We'll search by subject
|
||||
params: { page_size: "100" },
|
||||
});
|
||||
|
||||
// Use the page context to make API requests (authenticated via session)
|
||||
// Navigate to inbox first to ensure we have the right mailbox context
|
||||
await page.goto(`${API_URL}/api/v1.0/threads/?page_size=100`);
|
||||
const threadsData = JSON.parse(await page.locator("body").innerText());
|
||||
expect(threadsResp.ok()).toBe(true);
|
||||
const threadsData = await threadsResp.json();
|
||||
const targetThread = threadsData.results?.find(
|
||||
(t: any) => t.subject === "IMAP unread test"
|
||||
);
|
||||
|
||||
// If we found the thread, the read state should have been synced
|
||||
// (the IMAP STORE +FLAGS \Seen should have set read_at on the thread access)
|
||||
// The IMAP STORE +FLAGS \Seen should have set read_at on the thread access
|
||||
expect(targetThread).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -177,16 +173,20 @@ test.describe("Client Bridge IMAP", () => {
|
||||
|
||||
// Mark as unread via the webmail flag API
|
||||
// We need to find the thread ID first
|
||||
await page.goto(`${API_URL}/api/v1.0/threads/?page_size=100`);
|
||||
const threadsData = JSON.parse(await page.locator("body").innerText());
|
||||
const threadsResp = await page.request.get(`${API_URL}/api/v1.0/threads/`, {
|
||||
params: { page_size: "100" },
|
||||
});
|
||||
expect(threadsResp.ok()).toBe(true);
|
||||
const threadsData = await threadsResp.json();
|
||||
const targetThread = threadsData.results?.find(
|
||||
(t: any) => t.subject === targetSubject
|
||||
);
|
||||
expect(targetThread).toBeTruthy();
|
||||
|
||||
// Get mailbox ID for the user
|
||||
await page.goto(`${API_URL}/api/v1.0/mailboxes/`);
|
||||
const mailboxData = JSON.parse(await page.locator("body").innerText());
|
||||
const mailboxResp = await page.request.get(`${API_URL}/api/v1.0/mailboxes/`);
|
||||
expect(mailboxResp.ok()).toBe(true);
|
||||
const mailboxData = await mailboxResp.json();
|
||||
const mailbox = mailboxData.results?.find(
|
||||
(m: any) => m.local_part === "user.e2e.chromium"
|
||||
);
|
||||
|
||||
@@ -21,10 +21,6 @@ test.describe("Import Message", () => {
|
||||
const email = `user.e2e.${browserName}@example.local`;
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// As the database is fresh, there should be no threads and the Import messages button should be visible
|
||||
const noThreads = page.getByText("No threads.");
|
||||
await expect(page.getByRole("link", { name: "Import messages" })).toBeVisible();
|
||||
|
||||
const header = page.locator(".c__header");
|
||||
const settingsButton = header.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
|
||||
@@ -30,7 +30,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Wait for the data grid to load
|
||||
@@ -92,7 +92,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// First create a signature to edit
|
||||
@@ -136,7 +136,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// First create a signature to delete
|
||||
@@ -174,7 +174,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Create a signature
|
||||
@@ -208,7 +208,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Create a signature with default checkbox checked
|
||||
@@ -237,7 +237,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Create a default signature
|
||||
@@ -272,7 +272,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to signatures page
|
||||
const settingsButton = page.getByRole("button", { name: "More options" });
|
||||
await settingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Create first signature and set as default
|
||||
@@ -319,7 +319,7 @@ test.describe("Mailbox Signatures", () => {
|
||||
// Navigate to mailbox signatures page
|
||||
const userSettingsButton = page.getByRole("button", { name: "More options" });
|
||||
await userSettingsButton.click();
|
||||
await page.getByRole("menuitem", { name: "Signatures" }).click();
|
||||
await page.getByRole("menuitem", { name: "My signatures" }).click();
|
||||
await page.waitForURL("**/mailbox/*/signatures");
|
||||
|
||||
// Create a mailbox signature WITH is_default
|
||||
|
||||
+11
-8
@@ -10,16 +10,10 @@ import fs from 'fs';
|
||||
/**
|
||||
* Execute a npm command in the e2e-runner container.
|
||||
*/
|
||||
async function runNpmCommand(command: string, args: string[] = [], timeout: number = 1000): Promise<string> {
|
||||
async function runNpmCommand(command: string, args: string[] = []): Promise<string> {
|
||||
const commandArgs = [command, ...args].join(' ');
|
||||
|
||||
const fullCommand = `npm run ${commandArgs}`;
|
||||
|
||||
|
||||
if (timeout) {
|
||||
await new Promise((resolve) => { setTimeout(resolve, timeout) });
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(fullCommand, (error, stdout) => {
|
||||
if (error) reject(error);
|
||||
@@ -30,12 +24,21 @@ async function runNpmCommand(command: string, args: string[] = [], timeout: numb
|
||||
|
||||
/**
|
||||
* Reset the database by flushing all data (keeps schema) then
|
||||
* bootstrapping the demo data.
|
||||
* bootstrapping the demo data (without client-bridge data).
|
||||
*/
|
||||
export async function resetDatabase(): Promise<void> {
|
||||
await runNpmCommand('db:reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap client-bridge channels and IMAP test messages.
|
||||
* Only needed by client-bridge tests, separated to avoid the cost
|
||||
* of recreating EML blobs on every resetDatabase() call.
|
||||
*/
|
||||
export async function bootstrapClientBridge(): Promise<void> {
|
||||
await runNpmCommand('db:bootstrap-clientbridge');
|
||||
}
|
||||
|
||||
export const getStorageStatePath = (username: string): string => {
|
||||
return path.join(STORAGE_STATE_PATH, `user-${username}.json`);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user