]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Test] Add WebUI E2E scan flow test 5606/head
authorAlexander Moisseev <moiseev@mezonplus.ru>
Sun, 31 Aug 2025 14:50:17 +0000 (17:50 +0300)
committerAlexander Moisseev <moiseev@mezonplus.ru>
Fri, 5 Sep 2025 17:18:56 +0000 (20:18 +0300)
Covers reading counters before scanning, scanning two test messages,
verifying history, resetting history, and checking updated counters.

eslint.config.mjs
test/playwright/tests/scan.spec.mjs [new file with mode: 0644]

index 4ee3bb4f3a2f003fc901b4d1edfd4136b4cc3f31..80eca84197207a17292c9ce51a57970d23e48ced 100644 (file)
@@ -89,6 +89,7 @@ export default [
         files: ["test/playwright/tests/*.mjs"],
         rules: {
             "no-await-in-loop": "off", // Playwright operations in loops are often sequential and not independent
+            "no-empty-pattern": ["error", {allowObjectPatternsAsParameters: true}],
             "sort-keys": ["error", "asc", {minKeys: 4}]
         },
     },
diff --git a/test/playwright/tests/scan.spec.mjs b/test/playwright/tests/scan.spec.mjs
new file mode 100644 (file)
index 0000000..2e6447f
--- /dev/null
@@ -0,0 +1,148 @@
+import {expect, test} from "@playwright/test";
+import {login} from "../helpers/auth.mjs";
+
+test.describe.serial("Scan flow across WebUI tabs", () => {
+    let page = null;
+    let scannedSubjects = [];
+    const scannedBefore = {scanTab: 0, throughput: 0};
+
+    async function gotoTab(name) {
+        await page.locator(`#${name}_nav`).click();
+    }
+
+    function extractNumber(text) {
+        return parseInt(text.replace(/\D/g, ""), 10);
+    }
+
+    async function readScanTab() {
+        // Status tab → scanned widget
+        await gotoTab("status");
+        await page.waitForResponse((resp) => resp.url().includes("/stat") && resp.status() === 200);
+        const scannedWidget = page.locator("#statWidgets .widget[title*='scanned']");
+        await expect(scannedWidget).toBeVisible();
+        const scannedTitle = await scannedWidget.getAttribute("title");
+        return extractNumber(scannedTitle);
+    }
+
+    async function readThroughput() {
+        // Throughput tab → id="rrd-total-value"
+        await gotoTab("throughput");
+        await page.waitForResponse((resp) => resp.url().includes("/graph") && resp.status() === 200);
+        const throughputValue = page.locator("#rrd-total-value");
+        await expect(throughputValue).toBeVisible();
+        return extractNumber(await throughputValue.textContent());
+    }
+
+    async function expectAlertSuccess(expectedText) {
+        const alert = page.locator(".alert-success, .alert-modal.alert-success");
+        await expect(alert).toBeVisible();
+        const text = await alert.textContent();
+        expect(text).toContain(expectedText);
+        await expect(alert).not.toBeVisible({timeout: 10000});
+    }
+
+    test.beforeAll(async ({browser}, testInfo) => {
+        const context = await browser.newContext();
+        page = await context.newPage();
+        const {enablePassword} = testInfo.project.use.rspamdPasswords;
+        await login(page, enablePassword);
+    });
+
+    test.afterAll(async () => {
+        await page.close();
+    });
+
+    test.describe("Phase 1: before scanning", () => {
+        test("Read current Scanned counters", async () => {
+            scannedBefore.scanTab = await readScanTab();
+            scannedBefore.throughput = await readThroughput();
+        });
+    });
+
+    test.describe("Phase 2: scanning", () => {
+        test("Scan two test messages", async ({}, testInfo) => {
+            const {testId} = testInfo;
+            await gotoTab("scan");
+            const scanMessageBtn = page.locator('#scan button[data-upload="checkv2"]');
+            await expect(scanMessageBtn).toBeDisabled();
+
+            scannedSubjects = [];
+
+            for (let i = 1; i <= 2; i++) {
+                const timestamp = Date.now();
+                const msgId = `E2E-${i}-${timestamp}@example.com`;
+                const subject = `E2E Test ${i} ${testId}-${timestamp}`;
+                scannedSubjects.push(subject);
+
+                await page.locator("#scanMsgSource").fill(
+                    `Message-Id: ${msgId}\nFrom: test@example.com\nSubject: ${subject}\n\nTest body`
+                );
+                await scanMessageBtn.click();
+
+                await expectAlertSuccess("Data successfully scanned");
+                await expect(
+                    page.locator(`#historyTable_scan tbody tr:first-child td.footable-first-visible:has-text("${msgId}")`)
+                ).toBeVisible();
+            }
+        });
+    });
+
+    test.describe("Phase 3: after scanning", () => {
+        test("History shows scanned messages and can be reset", async () => {
+            await gotoTab("history");
+
+            // Check both scanned messages are present in reverse order
+            for (let i = 0; i < scannedSubjects.length; i++) {
+                const subject = scannedSubjects[scannedSubjects.length - 1 - i]; // reverse order
+                await expect(
+                    page.locator("#historyTable_history tbody tr").nth(i)
+                        .locator(`td:has-text("${subject}")`)
+                ).toBeVisible();
+            }
+
+            // Reset history
+            const resetBtn = page.locator("#resetHistory");
+            await expect(resetBtn).toBeVisible();
+            page.once("dialog", (dialog) => dialog.accept());
+            await resetBtn.click();
+
+            const updateHistoryBtn = page.locator("#updateHistory");
+            await expect(updateHistoryBtn).toBeDisabled();
+            await expect(updateHistoryBtn).not.toBeDisabled();
+
+            const rows = await page.locator("#historyTable_history tbody tr").count();
+            // Known bug: Rspamd leaves one row after history reset
+            expect([0, 1]).toContain(rows);
+        });
+    });
+
+    test.describe("Phase 4: counters after scanning", () => {
+        test("Status tab `Scanned` counter increased by 2", async () => {
+            const scanTab = await readScanTab();
+            expect(scanTab).toBe(scannedBefore.scanTab + 2);
+        });
+
+        test("Throughput `Total messages` counter increased", async ({}, testInfo) => {
+            testInfo.setTimeout(140000);
+            // Depending on row boundaries, throughput may show +2 or even +3
+            const targetValues = [
+                scannedBefore.throughput + 2,
+                scannedBefore.throughput + 3,
+            ];
+
+            let lastValue = null;
+            try {
+                await expect.poll(async () => {
+                    lastValue = await readThroughput();
+                    return targetValues.includes(lastValue);
+                }, {
+                    interval: 5000,
+                    // step = 1s, pdp_per_row = 60 → next row every 60s
+                    timeout: 125000,
+                }).toBeTruthy();
+            } catch (e) {
+                throw new Error(`Throughput counter should be one of [${targetValues.join(", ")}], got ${lastValue}`);
+            }
+        });
+    });
+});