feat(doku): Anleitung, PDF und Ende-zu-Ende-Abnahme
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Neun Kapitel unter docs/anleitung/ fuer Technik und Verwaltung, mit 35
Bildern aus `pnpm docs:screenshots` und einem Fehlerbehebungskapitel aus
echten Meldungen. `pnpm docs:pdf` macht daraus 72 Seiten A4 — hell
gedruckt, mit Deckblatt, Inhaltsverzeichnis und Seitenzahlen, ohne neue
Abhaengigkeit.

Die Ablaeufe pruefen ihre Voraussetzungen, bevor der erste startet:
antwortet ein gebauter Stand, steht der Seed-Bestand, und liest der
Server dieselbe Datenbank wie der Testprozess. Jeder Ablauf legt an, was
er braucht, und raeumt es weg. Vier neue kommen dazu: Hauptablauf,
Onboarding mit Vergleich der Zwischenablage, Rollen und 390 px auf
achtzehn Seiten.

Drei Fehler kamen dabei heraus. Das Projekt „handy" pruefte 412 px statt
der verlangten 390. Ein angemeldeter Benutzer ohne Recht erfuhr nie,
warum er abgewiesen wurde — die Meldung stand auf einer Seite, die ihn
sofort weiterleitet; jetzt gibt es /kein-zugriff. Und der
Verzeichnis-Ablauf schrieb die Attributzuordnung der ersten vorhandenen
Quelle um, in einer echten Installation also der produktiven.

101 Ablaeufe in INTERN und 97 in DIENSTLEISTUNG, beide ohne Fehlschlag.

Closes #18

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-26 11:03:02 +02:00
co-authored by Claude Opus 5
parent 9bd198a531
commit 10e272654f
104 changed files with 4527 additions and 299 deletions
+10 -28
View File
@@ -1,6 +1,6 @@
import { expect, test, type Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import "dotenv/config";
import { prisma } from "@/lib/db";
import { anmelden, SEED_TAG } from "./helfer";
/**
* Der Etikettendruck am gebauten Stand (#014).
@@ -14,34 +14,21 @@ import { prisma } from "@/lib/db";
* Die Aufloesung: 1 mm sind 96/25,4 px, ein `deviceScaleFactor` von 300/96 =
* 3,125 macht daraus 300 dpi. 62 mm werden so zu 62 · 96/25,4 · 3,125 ≈ 732
* Geraetepixeln — genau die Aufloesung, mit der ein Etikettendrucker arbeitet.
*
* Gedruckt wird `SEED_TAG` und nicht irgendein Code aus der Datenbank: dass es
* ihn gibt, prueft `tests/e2e/global-setup.ts` vor dem ersten Ablauf. Ein
* `findFirst` haette gegen eine frische Installation „kein Geraet“ gemeldet und
* gegen eine benutzte einen beliebigen Aufkleber gedruckt (#018).
*/
const DPI = 300;
const PX_JE_MM = 96 / 25.4;
async function anmelden(seite: Page) {
const email = process.env["SEED_ADMIN_EMAIL"];
const passwort = process.env["SEED_ADMIN_PASSWORD"];
if (!email || !passwort) throw new Error("SEED_ADMIN_EMAIL und SEED_ADMIN_PASSWORD fehlen.");
await seite.goto("/anmelden");
await seite.fill('input[name="email"]', email);
await seite.fill('input[name="passwort"]', passwort);
await Promise.all([seite.waitForURL("**/"), seite.click('button[type="submit"]')]);
}
/** Irgendein Tag-Code, den es in dieser Datenbank wirklich gibt. */
async function einTagCode(): Promise<string> {
const geraet = await prisma.geraet.findFirst({ select: { tagCode: true } });
if (!geraet) throw new Error("Kein Gerät in der Datenbank — erst pnpm db:seed.");
return geraet.tagCode;
}
test.describe("Etikettendruck", () => {
test("druckt ein Einzeletikett auf 62 × 29 mm", async ({ page }) => {
test.skip(test.info().project.name !== "desktop", "Das Seitenformat hängt nicht am Gerät.");
await anmelden(page);
const code = await einTagCode();
await page.goto(`/drucken/etiketten?layout=etikett&codes=${code}`);
await page.goto(`/drucken/etiketten?layout=etikett&codes=${SEED_TAG}`);
/*
* `page.pdf()` gilt in Chromium und beachtet `@page`. Gemessen wird die
@@ -69,7 +56,6 @@ test.describe("Etikettendruck", () => {
test("ein bei 300 dpi gerastertes Etikett lässt sich zurücklesen", async ({ browser }) => {
test.skip(test.info().project.name !== "desktop", "Der Decoder hängt nicht am Gerät.");
const code = await einTagCode();
/*
* Eigener Kontext: der `deviceScaleFactor` ist die Aufloesung, und die
@@ -84,7 +70,7 @@ test.describe("Etikettendruck", () => {
try {
const tab = await kontext.newPage();
await anmelden(tab);
await tab.goto(`/drucken/etiketten?layout=etikett&codes=${code}`);
await tab.goto(`/drucken/etiketten?layout=etikett&codes=${SEED_TAG}`);
const etikett = tab.locator("[data-etikett]").first();
await expect(etikett).toBeVisible();
const bild = await etikett.screenshot();
@@ -100,7 +86,7 @@ test.describe("Etikettendruck", () => {
* dazulegen koennte.
*/
const adresse = new URL(gelesen[0] ?? "");
expect(adresse.pathname).toBe(`/t/${code}`);
expect(adresse.pathname).toBe(`/t/${SEED_TAG}`);
expect(adresse.search).toBe("");
expect(adresse.hash).toBe("");
} finally {
@@ -157,7 +143,3 @@ function seitenmasseAusPdf(pdf: Buffer): { breite: number; hoehe: number } {
hoehe: Number(treffer[4]) - Number(treffer[2]),
};
}
test.afterAll(async () => {
await prisma.$disconnect();
});