1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
98 lines
4.3 KiB
JavaScript
98 lines
4.3 KiB
JavaScript
// @ts-check
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
/**
|
|
* Stufe 1 · Link-Crawler
|
|
*
|
|
* Pro zentraler Seite: alle internen <a href>-Links extrahieren und prüfen,
|
|
* dass das Ziel ≤ 399 antwortet. Externe Links (http(s):// fremder Host) und
|
|
* mailto:/tel:/javascript:/#-Links werden ignoriert.
|
|
*
|
|
* Das ist eine breitere „kein Link tot"-Garantie als der reine Slug-Check
|
|
* — fängt Links auf z. B. Modul-Info-Seiten, im Handbuch oder im Lehrplan,
|
|
* die wir in known-paths nicht explizit gelistet haben.
|
|
*/
|
|
|
|
const startSeiten = [
|
|
'/', // Landing
|
|
'/handbuch.html', // Handbuch (12 Sim-Beschreibungen + Lehrplanbezug)
|
|
'/lehrplan', // Lehrplan-Page
|
|
'/glossar', // Glossar-Index
|
|
'/mindmap', // Mindmap
|
|
'/forschung/', // Forschung
|
|
'/modul-klima', // Modul-Info-Seiten (eine genügt, sie nutzen das gleiche Partial)
|
|
'/modul-weltkueche',
|
|
'/teacher', // Lehrer-Cockpit (Login-Form, aber Links zum Drilldown da)
|
|
'/schueler', // Schüler-Dashboard
|
|
];
|
|
|
|
// Konfiguriert sequenziell — sonst hämmern wir uns selbst mit hunderten
|
|
// parallelen Requests an die gleiche Prod-Domain.
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
for (const seite of startSeiten) {
|
|
test(`Crawl · ${seite}`, async ({ page, request }, testInfo) => {
|
|
const res = await page.goto(seite);
|
|
expect(res, `Startseite ${seite} lädt nicht`).not.toBeNull();
|
|
expect(res.status(), `Startseite ${seite} antwortet ${res.status()}`).toBeLessThan(400);
|
|
|
|
// Alle hrefs sammeln — auch von Buttons mit data-href oder onclick, aber wir
|
|
// beschränken uns auf <a href>, das ist das einfachste und 90 % der Links.
|
|
const hrefs = await page.$$eval('a[href]', as =>
|
|
as.map(a => a.getAttribute('href')).filter(Boolean)
|
|
);
|
|
|
|
// Filtern: nur interne, sinnvolle Pfade.
|
|
// WICHTIG: relative URLs (z. B. `geografie-didaktik.html` auf /forschung/)
|
|
// müssen GEGEN page.url() aufgelöst werden — NICHT gegen origin, sonst
|
|
// landen wir fälschlich im Root.
|
|
const pageUrl = page.url();
|
|
const origin = new URL(pageUrl).origin;
|
|
const checkedSet = new Set();
|
|
const toCheck = [];
|
|
for (const h of hrefs) {
|
|
if (!h) continue;
|
|
if (h.startsWith('#')) continue; // Anker
|
|
if (h.startsWith('mailto:')) continue;
|
|
if (h.startsWith('tel:')) continue;
|
|
if (h.startsWith('javascript:')) continue;
|
|
if (h.startsWith('data:')) continue;
|
|
// Auflösen relativ zur aktuellen Seite (nicht zur origin!)
|
|
let absolute;
|
|
try { absolute = new URL(h, pageUrl).toString(); } catch { continue; }
|
|
if (!absolute.startsWith(origin)) continue; // externer Host raus
|
|
if (checkedSet.has(absolute)) continue;
|
|
checkedSet.add(absolute);
|
|
toCheck.push(absolute);
|
|
}
|
|
|
|
// Jeden Link einzeln prüfen — sequentiell, höchstens 30 pro Seite, damit
|
|
// wir bei sehr großen Index-Seiten nicht ewig laufen
|
|
const sample = toCheck.slice(0, 50);
|
|
const broken = [];
|
|
for (const url of sample) {
|
|
try {
|
|
const r = await request.get(url, { timeout: 15000 });
|
|
if (r.status() >= 400) broken.push({ url, status: r.status() });
|
|
} catch (e) {
|
|
broken.push({ url, status: 'TIMEOUT/ERROR: ' + (e.message || e).toString().split('\n')[0].slice(0, 80) });
|
|
}
|
|
}
|
|
// Bericht als Attachment, plus Soft-Assertion: wir melden jeden gebrochenen
|
|
// Link, akzeptieren aber bekannte Sonderfälle (404 für „geplant" laut
|
|
// bestehender known-paths.json wäre OK — hier ignorieren wir den expliziten
|
|
// Filter und stellen nur sicher dass keine 5xx kommen)
|
|
const fiveXX = broken.filter(b => typeof b.status === 'number' && b.status >= 500);
|
|
const fourXX = broken.filter(b => typeof b.status === 'number' && b.status >= 400 && b.status < 500);
|
|
|
|
console.log(`[${seite}] geprüft: ${sample.length} Links, 4xx: ${fourXX.length}, 5xx: ${fiveXX.length}`);
|
|
if (broken.length > 0) {
|
|
console.log(' Gebrochene Links:');
|
|
broken.forEach(b => console.log(` ${b.status} ${b.url}`));
|
|
}
|
|
|
|
// 5xx ist immer ein Bug. 4xx auch — aber wir nehmen nur die echten ans Bein
|
|
expect(broken, `Gebrochene Links auf ${seite}: ${JSON.stringify(broken, null, 2)}`).toHaveLength(0);
|
|
});
|
|
}
|