Erscheinungsbild
Tests
pnpm verify
Ein Befehl für die gesamte Qualitätskette. Die CI ruft bei jedem Pull Request exakt denselben Befehl auf, damit lokale und entfernte Ergebnisse nicht auseinanderlaufen.
Aktuell enthalten:
| Schritt | Werkzeug |
|---|---|
| Formatierung | Prettier |
| Linting | ESLint |
| Typprüfung | astro check |
| Tests | Vitest |
| Tests des Handlers | PHPUnit |
| Build | Astro |
| Fixture-Builds | Astro |
| Performance-Budget | Astro-Build |
| Post-Build | Vitest |
| End-to-End-Tests | Playwright |
Der letzte Schritt heißt test:e2e:built und startet nur Playwright, weil alle drei Ausgaben zu dem Zeitpunkt gebaut sind. pnpm test:e2e ist der Einzelaufruf, der selbst baut.
PHP gehört zur Kette
pnpm verify ruft PHPUnit für den PHP-Handler auf. Dafür braucht der Rechner PHP 8.3 und Composer; die CI richtet beides über shivammathur/setup-php ein. Der Handler ist Teil der Auslieferung — ihn in einer zweiten, selten gefahrenen Kette zu prüfen, hieße, ihn seltener zu prüfen.
Die Fixture-Builds bauen zwei vollständige Konfigurationen — einmal mit allen optionalen Funktionen aus, einmal mit allen an. Damit bleibt kein Feature-Flag ungetestet; Details unter Feature Flags.
In der CI bleibt der Bildcache unter node_modules/.astro/assets zwischen den Läufen erhalten. Der erste Build eines Laufs verarbeitet sonst jedes Quellbild in allen Breiten und Formaten neu, und das dauert bei einem Projekt mit echten Fotos länger als alle Tests zusammen (ADR 0019).
Sie unterscheiden sich zusätzlich in der Umgebung: „minimal" entsteht als Entwicklungsstand, „full" mit PUBLIC_ENVIRONMENT=production. Nur so sind beide Zustände der Indexierung auf dem ausgelieferten HTML geprüft — die Zusage „Preview bleibt draußen, Produktion kommt hinein" wäre sonst nur zur Hälfte belegt.
Unter tests/integration/ liegen Prüfungen, die einen echten Build starten. Sie sind langsamer als Unit-Tests, sichern dafür aber Akzeptanzkriterien ab — dass eine verbotene Konfiguration den Build tatsächlich abbricht. Nur dieser eine Build gehört dorthin: Dass die gültigen Fixtures bauen, belegen die Fixture-Builds derselben Kette, und ein zweiter, kalter Build aus Vitest heraus kostete Minuten, ohne etwas hinzuzufügen.
Unter tests/dist/ liegen Prüfungen auf dem ausgelieferten HTML. Sie laufen über eine eigene Vitest-Konfiguration (pnpm run test:dist), weil sie einen fertigen Build voraussetzen und deshalb erst danach an der Reihe sind. Geprüft werden alle drei Ausgaben — auch die Fixtures. Aktuell decken sie die Bildausgabe, Metadaten, strukturierte Daten, robots.txt, die Sitemap, den Ladeplan für Consent und Tracking sowie das Formular ab. Prüfungen der internen Links kommen mit den Inhaltstypen dazu.
Beim Formular ist die Post-Build-Ebene der einzige Ort, an dem sich zwei Dinge belegen lassen: dass jedes gerenderte Feld in der Allowlist aus src/content/forms.ts steht — ein Feld ohne Eintrag würde das Backend ablehnen, und zwar erst beim ersten echten Absender —, und dass eine Ausgabe ohne konfiguriertes Backend nicht die Ruine eines Formulars enthält.
Die End-to-End-Tests laufen zuletzt und brauchen einmalig installierte Browser:
sh
pnpm exec playwright install chromiumDetails unter End-to-End-Tests.
Der Vertrag als Prüfgegenstand
contract/form-submission.schema.json wird aus src/lib/forms/contract.ts erzeugt und liegt im Repository, ebenso server/forms/config/forms.json aus src/content/forms.ts. Je ein Vitest-File-Snapshot bricht ab, sobald eine der Dateien veraltet ist; pnpm forms:contract schreibt beide neu. Die Diffs gehören in denselben Commit wie die Änderung — sie sind die Stelle, an der eine Durchsicht bemerkt, dass Frontend und Backend gerade auseinandergehen.
Auf der PHP-Seite prüft server/forms/tests/ContractConformanceTest.php gegen dieselbe Datei, und zwar in beide Richtungen: Was das Schema annimmt, muss der Handler annehmen; was es ablehnt, muss er ablehnen. Eine Prüfung nur in eine Richtung bestünde auch ein Handler, der alles ablehnt. Dazu vergleicht er die Fehlercodes samt HTTP-Status mit x-errorCodes.
Der Handler
sh
pnpm run test:php # installiert die Abhängigkeiten und fährt PHPUnitDie Tests liegen bei ihrem Gegenstand unter server/forms/tests/. Sie benutzen eine eigene Allowlist (server/forms/tests/fixtures/forms.json) statt der erzeugten des Projekts: Ein Kundenprojekt, das sein Kontaktformular umbaut, darf die Tests des Handlers nicht rot färben. Dass die echte Datei lesbar und stimmig ist, prüft ein eigener Test — ohne sich auf einzelne Feldnamen zu stützen.
Performance-Budgets
Jeder Build misst die fertige Ausgabe und bricht ab, wenn eine Seite zu viel mitbringt. Die Grenzen stehen an einer Stelle, in BUDGETS in src/lib/deployment/budget.ts:
| Größe | Grenze | Gemessen |
|---|---|---|
| JavaScript je Seite | 40 KiB | gzip |
| CSS je Seite | 30 KiB | gzip |
| HTML je Seite | 30 KiB | gzip |
| Vorgeladene Schriften | 200 KiB | unkomprimiert |
Bilder mit priority | 1 | Anzahl |
Dazu eine harte Prüfung ohne einstellbare Grenze: Ein Bild ohne width und height bricht den Build ab, weil der Inhalt beim Laden springen würde.
Dazu einmal für die gesamte Ausgabe: höchstens sechs verschiedene Inline-Skripte — jedes braucht einen Hash in der Content Security Policy.
Gemessen wird mit gzip, weil die erzeugte .htaccess gzip ausliefert. Schriften werden unkomprimiert gemessen: woff2 ist es schon. Ab 80 Prozent eines Budgets erscheint eine Warnung, die den Build nicht aufhält.
Die Schriften des Starters liegen bei 175 von 200 KiB
Sechs vorgeladene Dateien aus zwei Familien, drei Schnitten und zwei Subsets. Die Warnung erscheint deshalb bei jedem Build und ist richtig. Wer sie loswerden will, reduziert Schnitte oder Subsets in src/lib/fonts.ts — nicht das Budget (ADR 0015).
Befunde werden nach Ursache zusammengefasst und nicht je Seite gemeldet: Eine Website hat auf jeder Seite dieselben Schriften, und achtzig gleichlautende Zeilen verdecken die eine, die woanders herkommt.
Was ein Budget nicht leistet: eine Zusage über Ladezeiten. Die hängt am Netz, am Gerät und am Hosting. Lighthouse bleibt sinnvoll — als Bericht, nicht als Gate, weil seine Werte auf einem Runner zwischen zwei Läufen um zweistellige Punktzahlen schwanken.
Deployment-Artefakte
tests/dist/deployment.test.ts prüft auf der fertigen Ausgabe, was sich anderswo nicht prüfen lässt: dass die Content Security Policy die Inline-Skripte abdeckt, die wirklich ausgeliefert wurden. Die Hashes werden dafür aus dem ausgelieferten HTML neu gebildet und mit der .htaccess verglichen. Ein Unit-Test kann belegen, dass ein übergebener Hash in der Datei landet — nicht, dass es der richtige ist. Genau dieser Fehler wäre still: Die Website lädt, und erst beim Umschalten auf eine erzwungene Richtlinie bleibt der Consent-Banner weg.
Dazu: dass bei github-pages keine .htaccess entsteht, dass der Deployment-Plan zur Projektkonfiguration passt und dass Google-Hosts nur bei aktivem Tracking genannt werden.
Der Initialisierungsprozess
Automatisch geprüft ist einiges: Die Unit-Tests unter tests/unit/starter/ nehmen jede Ableitung von den Antworten zur Konfiguration auseinander, und tests/integration/starter-init.test.ts hält den Plan gegen das Arbeitsverzeichnis — jeder Pfad darin muss existieren, und keine Seite, die den Init überlebt, darf auf ein Bild verweisen, das er entfernt.
Was diese Tests nicht sehen können, ist das Ergebnis: ob ein initialisiertes Projekt noch baut und ob pnpm verify darin grün ist. Genau dort liegen die teuren Fehler, weil sie niemanden im Starter stören und jeden im Kundenprojekt.
Deshalb gehört zu jeder Änderung am Init — und zu jeder Änderung an einer Datei, die er anfasst — ein Durchstich in einem Klon:
sh
git clone . /tmp/kunde-test # nimmt den committeten Stand
cd /tmp/kunde-test
pnpm install
pnpm starter:init --answers tests/fixtures/starter/mittwald-php.json
git diff --stat # der Init als eine überprüfbare Änderung
pnpm verify # muss grün seinBeide Antwort-Fixtures durchspielen, nicht nur eine: mittwald-php.json deckt den PHP-Handler und das Entfernen der Demo-Inhalte ab, pages-ohne-formular.json den Weg ohne Formular-Backend.
Dass dieser Schritt nötig ist, ist keine Vorsichtsmaßnahme, sondern Erfahrung. Der erste Durchstich fand vier Prüfungen, die stillschweigend den Starter festschrieben — die erwartete .htaccess bei einem Projekt auf GitHub Pages, ein vorrangiges Bild auf der Startseite, ein Datei-Snapshot, der den Handler-Ordner neu anlegte, und einen Styleguide, der an den Bildern der Demo-Startseite hing. Jede davon hätte in jedem Kundenprojekt pnpm verify rot gefärbt, im Starter selbst aber nie.
Geplante Inhalte
- Post-Build-Prüfungen zu internen Links — mit den Inhaltstypen