Files
Laufzettel/tests/e2e/scannen.spec.ts
T
Florian WildeandClaude Opus 5 5f1b0d1435
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(tags): QR-Aufkleber, Geraeteseite, Etikettendruck und Scanner
Der Einstieg, fuer den die Anwendung gebaut ist: scannen, sehen was zu tun ist,
loslegen. `/t/<tagCode>` kennt drei Faelle — registriertes Geraet, vorgedruckter
Tag, unbekannter Code —, die Geraeteseite zeigt Zuweisungen, Schnellstart und
Historie, und beides ist am Handy bedienbar.

Im QR steht ausschliesslich `{APP_URL}/t/{tagCode}`: kein Name, keine
Seriennummer, kein Besitzer. Ein Aufkleber haengt jahrelang sichtbar am Geraet
und wird von jedem Telefon gelesen; was darauf steht, ist veroeffentlicht. Die
Masse sind gemessen, nicht behauptet: ein echtes PDF liefert 62 x 29 mm und A4,
und ein bei 300 dpi gerastertes Etikett wird mit demselben Decoder
zurueckgelesen, den auch der Scanner benutzt.

Drei Defekte zeigte erst der gebaute Stand, alle an den Naehten zwischen den
parallel gebauten Teilen: „Starten“ setzte IN_ARBEIT und sprang nicht, „Vorlage
zuweisen“ bot Vorlagen an, die der Dienst danach ablehnte, und die Geraeteliste
verlinkte nichts. Der unabhaengige Review fand drei Blocker — darunter, dass der
Scanner nie die native BarcodeDetector-API benutzte und dass ein Unterpfad in
APP_URL aus dem gedruckten QR herausfiel.

Closes #14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 21:30:12 +02:00

66 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from "@playwright/test";
import "dotenv/config";
/**
* Der Einstieg ueber den Aufkleber (#014).
*
* Die Kamera laesst sich hier nicht pruefen — Chromium bekommt im Testlauf kein
* Bild, und ein gefaelschter Videostrom prueft am Ende die Attrappe. Geprueft
* wird deshalb das, was ohne Kamera trotzdem gelten muss: die Seite ist
* erreichbar, der Knopf in der Huelle fuehrt hin, und der **abgetippte** Code
* bringt den Techniker genauso ans Ziel wie der gescannte. Das ist kein
* Ersatzweg fuer den Notfall, sondern der Weg, den jeder geht, dessen Browser
* keine Kamera freigibt.
*/
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"]')]);
}
test.describe("Scannen", () => {
test("ist über den Knopf in der Hülle erreichbar", async ({ page }) => {
await anmelden(page);
await page.getByRole("link", { name: "Scannen" }).first().click();
await page.waitForURL("**/scannen");
await expect(page.getByRole("heading", { name: "Scannen" })).toBeVisible();
});
test("öffnet einen abgetippten Code", async ({ page }) => {
await anmelden(page);
await page.goto("/scannen");
// Ohne Kamera bleibt das Feld – und es nimmt auch Kleinschreibung an.
await page.getByLabel("Code eintippen").fill("lz-7k3f9q");
await page.getByRole("button", { name: "Öffnen" }).click();
await page.waitForURL("**/t/LZ-7K3F9Q");
});
test("lässt einen unsinnigen Code gar nicht erst abschicken", async ({ page }) => {
await anmelden(page);
await page.goto("/scannen");
await page.getByLabel("Code eintippen").fill("PC-001");
await expect(page.getByRole("button", { name: "Öffnen" })).toBeDisabled();
await expect(page.getByText(/Die Zeichen 0, O, 1 und I gibt es darin nicht/)).toBeVisible();
});
test("trägt am Handy ohne horizontales Scrollen", async ({ page, isMobile }) => {
test.skip(!isMobile, "Nur im Projekt „handy“.");
await anmelden(page);
await page.goto("/scannen");
const masse = await page.evaluate(() => ({
scroll: document.documentElement.scrollWidth,
sichtbar: document.documentElement.clientWidth,
}));
expect(masse.scroll).toBeLessThanOrEqual(masse.sichtbar);
});
});