MAESTRO: add analyzer page object and fixtures

This commit is contained in:
Mariusz Banach
2026-02-18 06:05:54 +01:00
parent 13918d92c7
commit 5e9eda4322
4 changed files with 331 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
Received: from mail.example.org (mail.example.org [203.0.113.10])
by mx.example.com with ESMTPS id 12345
for <user@example.com>; Tue, 17 Feb 2026 10:00:00 +0000
Received: from localhost (localhost [127.0.0.1])
by mail.example.org with SMTP id 67890
for <user@example.com>; Tue, 17 Feb 2026 09:59:00 +0000
Authentication-Results: mx.example.com;
spf=pass smtp.mailfrom=example.org;
dkim=pass header.d=example.org;
dmarc=pass
Subject: This is a test subject
with a folded line
From: "Sender Name" <sender@example.org>
To: user@example.com
Date: Tue, 17 Feb 2026 10:00:00 +0000
Message-ID: <1234@example.org>
Content-Type: multipart/alternative; boundary="boundary-123"
X-Forefront-Antispam-Report: CIP:203.0.113.10;CTRY:US;LANG:en;SCL:1;SRV:;
IPV:NLI;SFV:SKI;H:mail.example.org;CAT:NONE;SFTY:0.0;SFS:(0);DIR:INB;
X-Spam-Status: No, score=-0.1 required=5.0 tests=NONE
X-Spam-Level: **
X-Spam-Flag: NO
X-Spam-Report: Example report line one
Example report line two
X-Mimecast-Spam-Score: 1
X-Proofpoint-Spam-Details: rule=default, score=0
X-MS-Exchange-Organization-SCL: 1
--boundary-123
Content-Type: text/plain; charset=utf-8
This is the body.
X-Should-Not-Be-Parsed: nope
--boundary-123--

View File

@@ -0,0 +1,34 @@
Received: from mail.example.org (mail.example.org [203.0.113.10])
by mx.example.com with ESMTPS id 12345
for <user@example.com>; Tue, 17 Feb 2026 10:00:00 +0000
Received: from localhost (localhost [127.0.0.1])
by mail.example.org with SMTP id 67890
for <user@example.com>; Tue, 17 Feb 2026 09:59:00 +0000
Authentication-Results: mx.example.com;
spf=pass smtp.mailfrom=example.org;
dkim=pass header.d=example.org;
dmarc=pass
Subject: This is a test subject
with a folded line
From: "Sender Name" <sender@example.org>
To: user@example.com
Date: Tue, 17 Feb 2026 10:00:00 +0000
Message-ID: <1234@example.org>
Content-Type: multipart/alternative; boundary="boundary-123"
X-Forefront-Antispam-Report: CIP:203.0.113.10;CTRY:US;LANG:en;SCL:1;SRV:;
IPV:NLI;SFV:SKI;H:mail.example.org;CAT:NONE;SFTY:0.0;SFS:(0);DIR:INB;
X-Spam-Status: No, score=-0.1 required=5.0 tests=NONE
X-Spam-Level: **
X-Spam-Flag: NO
X-Spam-Report: Example report line one
Example report line two
X-Mimecast-Spam-Score: 1
X-Proofpoint-Spam-Details: rule=default, score=0
X-MS-Exchange-Organization-SCL: 1
--boundary-123
Content-Type: text/plain; charset=utf-8
This is the body.
X-Should-Not-Be-Parsed: nope
--boundary-123--

View File

@@ -0,0 +1,172 @@
import type { Download, Locator, Page } from "@playwright/test";
import fs from "fs/promises";
import path from "path";
const resolveMimeType = (fileName: string): string => {
const extension = path.extname(fileName).toLowerCase();
if (extension === ".eml") {
return "message/rfc822";
}
return "text/plain";
};
export class AnalyzerPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async goto(): Promise<void> {
await this.page.goto("http://localhost:3100");
}
async pasteHeaders(text: string): Promise<void> {
await this.headerInput().fill(text);
}
async dropFile(filePath: string): Promise<void> {
const resolvedPath = path.resolve(filePath);
const fileName = path.basename(resolvedPath);
const mimeType = resolveMimeType(fileName);
const fileBuffer = await fs.readFile(resolvedPath);
const base64 = fileBuffer.toString("base64");
await this.page.evaluate(
({ base64Data, name, type }) => {
const binary = atob(base64Data);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) {
bytes[index] = binary.charCodeAt(index);
}
const file = new File([bytes], name, { type });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
const dropZone = document.querySelector('[data-testid="file-drop-zone"]');
if (!dropZone) {
throw new Error("File drop zone not found");
}
const dragOver = new DragEvent("dragover", {
dataTransfer,
bubbles: true,
cancelable: true,
});
dropZone.dispatchEvent(dragOver);
const drop = new DragEvent("drop", {
dataTransfer,
bubbles: true,
cancelable: true,
});
dropZone.dispatchEvent(drop);
},
{ base64Data: base64, name: fileName, type: mimeType },
);
}
async clickAnalyse(): Promise<void> {
await this.analyseButton().click();
}
async pressCtrlEnter(): Promise<void> {
await this.page.keyboard.press("Control+Enter");
}
async selectTests(ids: number[]): Promise<void> {
await this.page.getByTestId("test-selector").waitFor({ state: "visible" });
for (const id of ids) {
const checkbox = this.page.getByTestId(`test-checkbox-${id}`);
await checkbox.scrollIntoViewIfNeeded();
if (!(await checkbox.isChecked())) {
await checkbox.check();
}
}
}
async deselectAll(): Promise<void> {
await this.page.getByTestId("deselect-all-tests").click();
}
async selectAll(): Promise<void> {
await this.page.getByTestId("select-all-tests").click();
}
async toggleDns(): Promise<void> {
await this.page.getByTestId("toggle-resolve").click();
}
async toggleDecodeAll(): Promise<void> {
await this.page.getByTestId("toggle-decode-all").click();
}
async waitForResults(): Promise<void> {
await this.page.getByTestId("report-container").waitFor({
state: "visible",
timeout: 30000,
});
}
getResultCards(): Locator {
return this.page.locator('[data-testid^="test-result-card-"]');
}
async expandCard(index: number): Promise<void> {
const toggle = this.getResultCards()
.nth(index)
.locator('[data-testid^="test-result-toggle-"]');
await toggle.waitFor({ state: "visible" });
const expanded = await toggle.getAttribute("aria-expanded");
if (expanded !== "true") {
await toggle.click();
}
}
async collapseCard(index: number): Promise<void> {
const toggle = this.getResultCards()
.nth(index)
.locator('[data-testid^="test-result-toggle-"]');
await toggle.waitFor({ state: "visible" });
const expanded = await toggle.getAttribute("aria-expanded");
if (expanded === "true") {
await toggle.click();
}
}
async searchReport(query: string): Promise<void> {
await this.page.getByTestId("report-search-input").fill(query);
}
async exportJson(): Promise<Download> {
const [download] = await Promise.all([
this.page.waitForEvent("download"),
this.page.getByTestId("report-export-json").click(),
]);
return download;
}
async exportHtml(): Promise<Download> {
const [download] = await Promise.all([
this.page.waitForEvent("download"),
this.page.getByTestId("report-export-html").click(),
]);
return download;
}
async clearCache(): Promise<void> {
await this.page.getByRole("button", { name: "Clear Cache" }).click();
}
getCaptchaModal(): Locator {
return this.page.getByTestId("captcha-challenge");
}
private headerInput(): Locator {
return this.page.getByRole("textbox", { name: "Header Input" });
}
private analyseButton(): Locator {
return this.page.getByRole("button", { name: "Analyse Headers" });
}
}