taskmanager/scripts/check-demo.ts

163 lines
6.8 KiB
TypeScript

import { config } from "dotenv";
import { chromium, request } from "@playwright/test";
import assert from "node:assert/strict";
import { mkdir, writeFile } from "node:fs/promises";
config({ path: ".env.demo.local", quiet: true });
async function main() {
if (
process.env.DEMO_MODE !== "true" ||
process.env.NEXTAUTH_URL !== "http://localhost:3110"
)
throw new Error("Local demo configuration required");
const baseURL = "http://localhost:3110";
const api = await request.newContext({
baseURL,
extraHTTPHeaders: { Origin: baseURL },
});
const browser = await chromium.launch();
try {
assert.equal((await api.get("/api/health")).status(), 200);
const csrf = await (await api.get("/api/auth/csrf")).json();
await api.post("/api/auth/callback/credentials", {
form: {
csrfToken: csrf.csrfToken,
email: "admin@example.test",
password: process.env.DEMO_PASSWORD!,
json: "true",
},
});
assert.equal(
(await (await api.get("/api/auth/session")).json()).user.id,
"demo-admin",
);
// A valid tiny PDF fixture also exercises the read-only container's upload volume.
const objects = [
"<< /Type /Catalog /Pages 2 0 R >>",
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 400 200] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>",
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
];
const stream =
"BT /F1 16 Tf 30 140 Td (Taskmanager - Testprotokoll) Tj 0 -30 Td (Private Datei erfolgreich gespeichert.) Tj ET";
objects.push(
`<< /Length ${Buffer.byteLength(stream)} >>\nstream\n${stream}\nendstream`,
);
let pdf = "%PDF-1.4\n";
const offsets: number[] = [0];
objects.forEach((object, i) => {
offsets.push(Buffer.byteLength(pdf));
pdf += `${i + 1} 0 obj\n${object}\nendobj\n`;
});
const xref = Buffer.byteLength(pdf);
pdf += `xref\n0 6\n0000000000 65535 f \n${offsets
.slice(1)
.map((n) => `${String(n).padStart(10, "0")} 00000 n \n`)
.join(
"",
)}trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
const detail = await (await api.get("/api/tasks/demo-task-1")).json();
if (
!detail.files?.some(
(f: { name: string }) => f.name === "Testprotokoll.pdf",
)
) {
const upload = await api.post("/api/tasks/demo-task-1/files", {
multipart: {
file: {
name: "Testprotokoll.pdf",
mimeType: "application/pdf",
buffer: Buffer.from(pdf),
},
},
});
assert.equal(upload.status(), 201, await upload.text());
const file = await upload.json();
assert.deepEqual(
await (await api.get(file.path)).body(),
Buffer.from(pdf),
);
}
const durations: number[] = [];
await Promise.all(
Array.from({ length: 5 }, async () => {
for (let i = 0; i < 10; i++) {
const start = performance.now();
assert.equal((await api.get("/api/tasks?limit=25")).status(), 200);
durations.push(performance.now() - start);
}
}),
);
durations.sort((a, b) => a - b);
const page = await browser.newPage({
storageState: await api.storageState(),
viewport: { width: 1440, height: 1000 },
});
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto(`${baseURL}/dashboard/tasks`);
await page
.getByRole("link", { name: "Heizungsanlage prüfen", exact: true })
.waitFor();
await mkdir("test-results", { recursive: true });
await page.screenshot({
path: "test-results/demo-desktop.png",
fullPage: true, animations: "disabled",
});
await page.goto(`${baseURL}/dashboard/access`);
await page.getByRole("heading", { name: "Gruppen & Rechte" }).waitFor();
await page.getByRole("button", { name: "Haustechnik", exact: true }).waitFor();
await page.screenshot({ path: "test-results/demo-rbac.png", fullPage: true, animations: "disabled" });
await page.getByRole("button", { name: "Haustechnik", exact: true }).click();
await page.getByRole("dialog", { name: "Haustechnik", exact: true }).waitFor();
await page.screenshot({ path: "test-results/demo-group-detail.png", fullPage: true, animations: "disabled" });
await page.getByRole("dialog", { name: "Haustechnik", exact: true }).getByRole("button", { name: "Schließen" }).click();
for (const [route, heading] of [
["/dashboard", "Übersicht"],
["/dashboard/tasks/demo-task-1", "Heizungsanlage prüfen"],
["/dashboard/users", "Benutzer"],
["/dashboard/settings", "Import & Integrationen"],
["/dashboard/calendar", "Kalender"],
]) {
await page.goto(`${baseURL}${route}`);
if (route !== "/dashboard") await page.getByRole("heading", { name: heading, exact: true }).waitFor();
await page.waitForLoadState("networkidle");
await page.screenshot({ path: `test-results/demo-${route.split("/").pop() || "overview"}.png`, fullPage: true, animations: "disabled" });
}
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(`${baseURL}/dashboard/tasks`);
await page.getByRole("link", { name: "Heizungsanlage prüfen", exact: true }).waitFor();
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
await page.screenshot({ path: "test-results/demo-mobile.png", fullPage: true, animations: "disabled" });
await page.getByRole("button", { name: "Neue Aufgabe", exact: true }).click();
await page.getByRole("dialog", { name: "Neue Aufgabe", exact: true }).waitFor();
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
await page.screenshot({ path: "test-results/demo-mobile-dialog.png", fullPage: false, animations: "disabled" });
await page.getByRole("dialog", { name: "Neue Aufgabe", exact: true }).getByRole("button", { name: "Abbrechen" }).click();
await page.setViewportSize({ width: 1440, height: 1000 });
await page.emulateMedia({ colorScheme: "dark" });
await page.reload();
await page.getByRole("link", { name: "Heizungsanlage prüfen", exact: true }).waitFor();
await page.screenshot({ path: "test-results/demo-dark.png", fullPage: true, animations: "disabled" });
assert.deepEqual(errors, []);
const result = {
requests: durations.length,
concurrency: 5,
medianMs: Math.round(durations[25]),
p95Ms: Math.round(durations[47]),
browserErrors: errors,
};
await writeFile(
"test-results/demo-check.json",
JSON.stringify(result, null, 2),
);
console.log(JSON.stringify(result));
} finally {
await api.dispose();
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});