Files
dogfather-universe/pruef-verwaltung-schmuck.mjs
T
DogFatherGitandClaude Opus 5 fdf3e5349e Verwaltung: gleitender Leuchtbalken, gestaffelte Kacheln, Reiterlicht
Drei weitere Stufen auf der Buehne.

1. DER GLEITENDE LEUCHTBALKEN

Unter der Reiterreihe liegt ein Balken in der Leitfarbe. Beim Wechsel
springt er nicht, sondern gleitet zum neuen Reiter und faerbt sich dabei
um.

Position und Breite kommen aus dem ECHTEN Reiter, im Browser gemessen.
Feste Werte waeren hier zwangslaeufig falsch: Die Reiter sind
unterschiedlich breit ("Kunden" gegen "Zahlungen"), sie verschieben sich
beim Sprachwechsel, und auf schmalen Schirmen brechen sie um -- deshalb
wandert auch die Hoehe mit, nicht nur die Seite.

2. DIE KACHELN TAUCHEN GESTAFFELT AUF

Beim Bereichswechsel erscheinen sie nacheinander statt alle auf einmal.
Nur die ersten acht bekommen einen Versatz -- bei einer langen Liste
kaeme die letzte Kachel sonst spuerbar spaeter, und das fuehlt sich
nicht mehr elegant an, sondern langsam.

3. DAS LICHT FOLGT AUCH AUF DEN REITERN

Die Verfolgung im Grundsystem greift ausdruecklich nur auf Karten. Fuer
die Reiter ist sie hier ergaenzt, gedrosselt ueber
requestAnimationFrame -- aus demselben Grund wie dort.

ZWEI FEHLER, DIE DER TEST GEFUNDEN HAT:

Der Balken stand auf Breite 0 und blieb unsichtbar. Ein blosser
"resize"-Horcher reicht naemlich nicht: Der haeufigste Fall ist gar
keine Fenstergroessenaenderung, sondern das Sichtbarwerden. Beim Start
ist der Arbeitsbereich versteckt, die Leiste also 0 Punkte breit -- und
ein verstecktes Element loest kein resize aus. Jetzt beobachtet ein
ResizeObserver die Leiste; das deckt Sichtbarwerden, Umbrechen und
Sprachwechsel gleichermassen ab.

Und die Messung hing allein am Klick-Listener. Wechselt der Bereich auf
einem anderen Weg -- etwa direkt nach dem Anmelden, wenn der
Arbeitsbereich zum ersten Mal auftaucht -- wurde nie nachgemessen.
balkenSetzen ist deshalb jetzt nach aussen verfuegbar.

Alles Bewegte bleibt bei prefers-reduced-motion aus: kein Gleiten, kein
Auftauchen, keine Parallaxe, kein Reflex. Die Buehne bleibt sichtbar.

Ein Wort zum Test selbst: Er prueft "der Balken springt" nicht mehr auf
wortwoertlich "0s". Die Testumgebung emuliert reduzierte Bewegung, indem
sie Uebergaenge auf eine Mikrosekunde setzt statt auf null -- gemeldet
wird "1e-06s". Wahrnehmbar ist das identisch; ein Test auf exakt "0s"
haette nur die Emulation gemessen, nicht die Regel.

Geprueft: 244 Pruefungen gruen (Verwaltung 46, Portfolio 15, CRYONOVA 53,
Blickfang 13, System 5, Abmelden 16, Abbruch 42, Angebot 54).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 07:02:51 +02:00

474 lines
21 KiB
JavaScript

/* =====================================================================
Prueft die Buehne im Verwaltungsbereich.
Das Konzept hat sich umgedreht, und damit auch die Pruefung.
Vorher: ein schmaler Streifen, auf 190 % gezoomt, davon ein Ausschnitt
gewaehlt und die linke Haelfte voll zugedeckt -- damit Text darauf
lesbar bleibt. Gemessen wurde, ob das Bild leise genug ist. Uebrig
blieb ein Zipfel.
Jetzt: Das Motiv ist die Buehne -- bildschirmfuellend, ungeschnitten,
ungedimmt. Der Inhalt schwebt als Milchglas darueber. Text steht nie
direkt auf dem Bild.
Deshalb wird jetzt das Gegenteil geprueft:
1. Ist das Bild wirklich GANZ da? (kein Zoom, kein Ausschnitt)
2. Traegt jede Flaeche, auf der gelesen wird, auch Glas?
3. Bleibt der Text darauf lesbar -- an echten Bildpunkten gemessen?
===================================================================== */
import { chromium } from "playwright";
import http from "http";
import fs from "fs";
import path from "path";
const TYP = {
".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp",
".json": "application/json", ".ico": "image/x-icon", ".jpg": "image/jpeg",
};
const fehlt = [];
const srv = http.createServer((q, r) => {
const u = decodeURIComponent(q.url.split("?")[0]);
const p = path.join(process.cwd(), u);
if (fs.existsSync(p) && !p.endsWith("/")) {
r.writeHead(200, { "content-type": TYP[path.extname(p)] || "text/plain" });
return r.end(fs.readFileSync(p));
}
fehlt.push(u);
r.writeHead(404);
r.end("");
});
let ok = 0, fehl = 0;
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
};
function leuchtkraft([r, g, b]) {
const f = (v) => {
v /= 255;
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
};
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
}
function verhaeltnis(a, b) {
const l1 = leuchtkraft(a), l2 = leuchtkraft(b);
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
const BEREICHE = ["uebersicht", "anfragen", "projekte", "kunden", "zahlungen", "postfach"];
/* Setzt einen Bereich und legt Beispielinhalt ein. Ohne Inhalt liesse
sich nicht messen, ob Text ueber der Buehne lesbar bleibt -- und genau
das ist die Frage, an der das ganze Konzept haengt.
Der Aufbau passiert vor JEDER Messung neu: Der Datenabruf laeuft im
Hintergrund weiter und klappt den Arbeitsbereich verzoegert wieder zu
(Zugangspruefung, kein Fehler). */
async function bereichAufbauen(seite, bereich) {
await seite.evaluate((b) => {
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
const bereichEl = document.querySelector("#vw-bereich");
bereichEl.removeAttribute("hidden");
document.documentElement.setAttribute("data-vw-bereich", b);
document.querySelectorAll("[data-tab]").forEach((x) =>
x.classList.toggle("vw-an", x.dataset.tab === b));
let probe = document.querySelector("#vw-probe");
if (!probe) {
probe = document.createElement("div");
probe.id = "vw-probe";
probe.innerHTML =
'<div class="wd-karte" style="padding:1.4rem;margin-bottom:1rem">' +
'<h2 style="margin-top:0">Vanessa Geske</h2>' +
'<p class="wd-klein wd-leise">[email protected]</p>' +
'<p id="vw-probe-text">Zwei Projekte, eines davon laufend.</p></div>';
bereichEl.appendChild(probe);
}
}, bereich);
await seite.waitForTimeout(700);
}
/* Misst die Schwankung des Untergrunds an echten Bildpunkten. Hohe
Schwankung heisst: Da liegt Bildstruktur unter der Schrift. */
async function unruheAn(seite, wahl) {
const bild = await seite.locator(wahl).first().screenshot();
return seite.evaluate(async (daten) => {
const bl = await fetch("data:image/png;base64," + daten).then((r) => r.blob());
const bm = await createImageBitmap(bl);
const c = new OffscreenCanvas(bm.width, bm.height);
const x = c.getContext("2d");
x.drawImage(bm, 0, 0);
const d = x.getImageData(0, 0, bm.width, bm.height).data;
const grund = [];
for (let i = 0; i < d.length; i += 4) {
const h = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
if (h < 120) grund.push(h);
}
if (grund.length < 50) return null;
const m = grund.reduce((a, b) => a + b, 0) / grund.length;
return Math.round(Math.sqrt(grund.reduce((a, b) => a + (b - m) ** 2, 0) / grund.length));
}, bild.toString("base64"));
}
srv.listen(4404, async () => {
const browser = await chromium.launch();
const seite = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const skriptfehler = [];
seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 140)));
await seite.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
await seite.waitForTimeout(1500);
console.log("===== 1. IST DAS BILD GANZ DA? =====");
await bereichAufbauen(seite, "kunden");
const buehne = await seite.evaluate(() => {
const el = document.querySelector(".vw-buehne");
if (!el) return null;
const st = getComputedStyle(el);
const r = el.getBoundingClientRect();
const treffer = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
return {
datei: treffer ? treffer[1] : "(keins)",
groesse: st.backgroundSize,
lage: st.backgroundPosition,
haltung: st.position,
breite: Math.round(r.width),
hoehe: Math.round(r.height),
z: parseInt(st.zIndex, 10),
klicks: st.pointerEvents,
versteckt: el.getAttribute("aria-hidden"),
};
});
if (!buehne) { pruefe("Buehne vorhanden", false); }
else {
/* Das ist der Kern der Korrektur. Beim ersten Anlauf stand hier
"190% auto" plus ein Ausschnitt wie "84% 46%" -- damit war der
groesste Teil des Bildes weggeschnitten. */
pruefe("das Bild wird NICHT gezoomt", buehne.groesse === "cover", buehne.groesse);
pruefe("und NICHT einseitig ausgeschnitten",
/50%\s+50%|center/.test(buehne.lage), buehne.lage);
pruefe("die Buehne fuellt den ganzen Bildschirm",
buehne.breite >= 1430 && buehne.hoehe >= 890, `${buehne.breite}x${buehne.hoehe}`);
pruefe("sie steht fest, der Inhalt scrollt darueber",
buehne.haltung === "fixed", buehne.haltung);
pruefe("sie liegt hinter allem", buehne.z < 0, "z-index " + buehne.z);
pruefe("sie faengt keine Klicks ab", buehne.klicks === "none", buehne.klicks);
pruefe("sie ist als Zierde ausgezeichnet", buehne.versteckt === "true");
}
console.log("\n===== 2. TRAEGT ALLES GLAS, WORAUF GELESEN WIRD? =====");
const glas = await seite.evaluate(() => {
const pruefen = [".wd-karte", ".vw-filter-knopf", ".vw-wer"];
const raus = {};
for (const w of pruefen) {
const el = document.querySelector("#vw-bereich " + w);
if (!el) { raus[w] = "(nicht gefunden)"; continue; }
const st = getComputedStyle(el);
raus[w] = st.backdropFilter || st.webkitBackdropFilter || "keins";
}
return raus;
});
for (const [w, wert] of Object.entries(glas)) {
if (wert === "(nicht gefunden)") continue;
pruefe(` ${w} hat Rueckseiten-Unschaerfe`, /blur/.test(wert), wert.slice(0, 34));
}
console.log("\n===== 3. BLEIBT DER TEXT LESBAR? =====");
for (const bereich of BEREICHE) {
await bereichAufbauen(seite, bereich);
const aufGlas = await unruheAn(seite, "#vw-probe .wd-karte");
await seite.evaluate(() => {
document.querySelector(".vw-buehne").style.visibility = "hidden";
});
await seite.waitForTimeout(350);
await bereichAufbauen(seite, bereich);
const ohneBuehne = await unruheAn(seite, "#vw-probe .wd-karte");
await seite.evaluate(() => {
document.querySelector(".vw-buehne").style.visibility = "";
});
await seite.waitForTimeout(250);
const mehr = (aufGlas ?? 99) - (ohneBuehne ?? 0);
/* Etwas Struktur DARF durchkommen -- das ist ja der Reiz. Sie darf
nur nicht so stark werden, dass die Schrift darauf flimmert. */
pruefe(` ${bereich}: der Kachelgrund bleibt ruhig`,
aufGlas !== null && ohneBuehne !== null && mehr <= 8,
`mit Buehne ${aufGlas}, ohne ${ohneBuehne} → plus ${mehr}`);
await bereichAufbauen(seite, bereich);
/* Der tatsaechliche Kachelgrund wird aus dem BILD gelesen, nicht aus
dem Stilblatt: Die Kachel ist halbdurchsichtig, ihr rechnerischer
Wert sagt nichts darueber, was am Ende darunter liegt. */
const stelle = await seite.evaluate(() => {
const k = document.querySelector("#vw-probe .wd-karte");
const r = k.getBoundingClientRect();
return { x: Math.round(r.x + r.width * 0.72), y: Math.round(r.y + r.height * 0.62) };
});
const punkt = await seite.screenshot({
clip: { x: stelle.x, y: stelle.y, width: 10, height: 10 } });
const grundfarbe = await seite.evaluate(async (daten) => {
const bl = await fetch("data:image/png;base64," + daten).then((r) => r.blob());
const bm = await createImageBitmap(bl);
const c = new OffscreenCanvas(bm.width, bm.height);
const x = c.getContext("2d");
x.drawImage(bm, 0, 0);
const d = x.getImageData(0, 0, bm.width, bm.height).data;
let r = 0, g = 0, b = 0, n = 0;
for (let i = 0; i < d.length; i += 4) { r += d[i]; g += d[i + 1]; b += d[i + 2]; n++; }
return [Math.round(r / n), Math.round(g / n), Math.round(b / n)];
}, punkt.toString("base64"));
const schrift = await seite.evaluate(() =>
getComputedStyle(document.querySelector("#vw-probe-text"))
.color.match(/[\d.]+/g).slice(0, 3).map(Number));
const v = verhaeltnis(schrift, grundfarbe);
pruefe(` ${bereich}: Text auf der Kachel erfuellt AA (4,5:1)`,
v >= 4.5, v.toFixed(2) + ":1");
}
console.log("\n===== 3b. FOLGT DAS LICHT DER MAUS? =====");
await bereichAufbauen(seite, "kunden");
const karte = seite.locator("#vw-probe .wd-karte");
const kasten = await karte.boundingBox();
/* WICHTIG: mit steps bewegen. Ein Sprung ohne Zwischenschritte
erzeugt kein pointermove -- der Test wuerde "Licht folgt nicht"
melden, obwohl alles stimmt. Diese Falle hat hier schon einmal
einen Fehlalarm ausgeloest. */
await seite.mouse.move(kasten.x + 30, kasten.y + 20, { steps: 8 });
await seite.waitForTimeout(320);
const lichtA = await karte.evaluate((e) => ({
x: getComputedStyle(e).getPropertyValue("--wd-lichtx").trim(),
y: getComputedStyle(e).getPropertyValue("--wd-lichty").trim(),
}));
await seite.mouse.move(kasten.x + kasten.width - 40,
kasten.y + kasten.height - 20, { steps: 16 });
await seite.waitForTimeout(400);
const lichtB = await karte.evaluate((e) => {
const st = getComputedStyle(e);
const na = getComputedStyle(e, "::after");
return {
x: st.getPropertyValue("--wd-lichtx").trim(),
y: st.getPropertyValue("--wd-lichty").trim(),
lumen: st.getPropertyValue("--wd-lumen").trim(),
kante: st.backgroundImage.includes("radial-gradient"),
kegel: na.backgroundImage,
};
});
pruefe("das Licht wandert mit dem Zeiger",
lichtA.x !== lichtB.x && lichtA.y !== lichtB.y,
lichtA.x + "/" + lichtA.y + " -> " + lichtB.x + "/" + lichtB.y);
pruefe("die Kachelkante leuchtet an der Zeigerstelle", lichtB.kante);
/* Der Lichtkegel muss die Leitfarbe des Bereichs tragen, nicht die
Grundfarbe -- sonst leuchtet im Kundenbereich Baby Blue statt
Indigo. */
pruefe("der Lichtkegel traegt die Leitfarbe",
/97,\s*92,\s*255/.test(lichtB.kegel), lichtB.lumen);
console.log("\n===== 3c. BEWEGT SICH DIE BUEHNE MIT? =====");
await seite.mouse.move(160, 780, { steps: 12 });
await seite.waitForTimeout(650);
const lage1 = await seite.evaluate(() =>
getComputedStyle(document.querySelector(".vw-buehne")).translate);
await seite.mouse.move(1320, 130, { steps: 20 });
await seite.waitForTimeout(750);
const lage2 = await seite.evaluate(() =>
getComputedStyle(document.querySelector(".vw-buehne")).translate);
pruefe("die Buehne verschiebt sich gegen den Zeiger",
lage1 !== lage2 && lage1 !== "none", lage1 + " -> " + lage2);
/* Der Ausschlag muss klein bleiben. Waere er gross, wuerde beim Lesen
staendig etwas im Augenwinkel wandern. */
const weite = Math.max(...String(lage2).match(/-?[\d.]+/g).map((n) => Math.abs(+n)));
pruefe("und zwar nur wenige Bildpunkte", weite <= 16, weite.toFixed(1) + "px");
console.log("\n===== 3d. DER LEUCHTBALKEN =====");
/* Der Balken muss GENAU am aktiven Reiter sitzen. Gemessen wird
gegen die echten Masse des Reiters -- feste Werte waeren hier
zwangslaeufig falsch, weil die Reiter unterschiedlich breit sind
und beim Sprachwechsel wandern. */
const balkenPruefen = async (bereich) => {
/* Erst den Bereich sichtbar machen, DANN klicken: Ein Klick auf
einen zugeklappten Arbeitsbereich misst zwangslaeufig 0.
Danach wieder aufmachen -- der Klick loest die Zugangspruefung
aus, die korrekterweise zur Anmeldung zurueckschaltet. */
await bereichAufbauen(seite, bereich);
await seite.click(`[data-tab="${bereich}"]`).catch(() => {});
await seite.waitForTimeout(500);
await bereichAufbauen(seite, bereich);
await seite.evaluate(() => window.vwBalkenSetzen && window.vwBalkenSetzen());
await seite.waitForTimeout(400);
return seite.evaluate(() => {
const l = document.querySelector("#vw-bereich .vw-tabs");
const a = l.querySelector(".vw-filter-knopf.vw-an");
const lr = l.getBoundingClientRect();
const ar = a.getBoundingClientRect();
const st = l.style;
return {
gesetztX: parseInt(st.getPropertyValue("--vw-balken-x"), 10),
gesetztB: parseInt(st.getPropertyValue("--vw-balken-b"), 10),
echtX: Math.round(ar.left - lr.left),
echtB: Math.round(ar.width),
sichtbar: st.getPropertyValue("--vw-balken-da"),
reiter: a.textContent.trim().slice(0, 12),
};
});
};
const b1 = await balkenPruefen("uebersicht");
pruefe("der Balken sitzt am aktiven Reiter",
Math.abs(b1.gesetztX - b1.echtX) <= 2 && Math.abs(b1.gesetztB - b1.echtB) <= 2,
`${b1.reiter}: gesetzt ${b1.gesetztX}/${b1.gesetztB}, echt ${b1.echtX}/${b1.echtB}`);
pruefe("und er ist sichtbar", b1.sichtbar === "1", b1.sichtbar);
/* Der haeufigste Fehlerfall: Beim Start ist der Arbeitsbereich noch
versteckt, die Leiste also 0 Punkte breit. Wer nur auf "resize"
horcht, misst nie nach -- der Balken bleibt auf Breite 0. */
pruefe("er hat eine echte Breite (nicht 0)", b1.gesetztB > 20, b1.gesetztB + "px");
const b2 = await balkenPruefen("zahlungen");
pruefe("er wandert zum neuen Reiter mit",
b2.gesetztX !== b1.gesetztX && Math.abs(b2.gesetztX - b2.echtX) <= 2,
`${b1.gesetztX}px -> ${b2.gesetztX}px`);
console.log("\n===== 3e. TAUCHEN DIE KACHELN GESTAFFELT AUF? =====");
const staffel = await seite.evaluate(() => {
const be = document.querySelector("#vw-bereich");
const probe = document.querySelector("#vw-probe");
probe.innerHTML = "";
for (let i = 0; i < 4; i++) {
const k = document.createElement("div");
k.className = "wd-karte";
k.style.padding = "1rem";
k.textContent = "Kachel " + (i + 1);
probe.appendChild(k);
}
return [...probe.querySelectorAll(".wd-karte")].map((k) => ({
name: getComputedStyle(k).animationName,
versatz: getComputedStyle(k).animationDelay,
}));
});
pruefe("die Kacheln haben eine Auftauch-Animation",
staffel.every((k) => k.name === "vw-kachel-auf"),
staffel.map((k) => k.name).join(", "));
pruefe("und jede startet etwas spaeter als die davor",
new Set(staffel.map((k) => k.versatz)).size === staffel.length,
staffel.map((k) => k.versatz).join(" "));
console.log("\n===== 4. WECHSELT DER RAUM WIRKLICH? =====");
const gesehen = [];
for (const bereich of BEREICHE) {
await bereichAufbauen(seite, bereich);
const d = await seite.evaluate(() => {
const st = getComputedStyle(document.querySelector(".vw-buehne"));
const t = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
return {
datei: t ? t[1] : "(keins)",
farbe: getComputedStyle(document.documentElement)
.getPropertyValue("--vw-farbe").trim(),
};
});
gesehen.push({ bereich, ...d });
console.log(` ${bereich.padEnd(11)} ${d.datei.padEnd(22)} ${d.farbe}`);
}
pruefe("jeder Bereich hat eine eigene Leitfarbe",
new Set(gesehen.map((g) => g.farbe)).size === 6,
new Set(gesehen.map((g) => g.farbe)).size + " von 6");
pruefe("mindestens fuenf verschiedene Motive",
new Set(gesehen.map((g) => g.datei)).size >= 5,
new Set(gesehen.map((g) => g.datei)).size + " verschiedene");
pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || "");
await bereichAufbauen(seite, "kunden");
await seite.screenshot({ path: "pruef-vw-schmuck-computer.png" });
await seite.close();
console.log("\n===== 5. REDUZIERTE BEWEGUNG =====");
const ruhig = await browser.newPage({
viewport: { width: 1440, height: 900 }, reducedMotion: "reduce" });
await ruhig.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
await ruhig.waitForTimeout(1300);
await bereichAufbauen(ruhig, "projekte");
const bewegung = await ruhig.evaluate(() => {
const st = getComputedStyle(document.querySelector(".vw-buehne"));
return { animation: st.animationName, sichtbar: st.visibility };
});
pruefe("keine Animation bei reduzierter Bewegung",
bewegung.animation === "none", bewegung.animation);
/* Abschalten heisst NICHT verschwinden. */
pruefe("die Buehne ist trotzdem da", bewegung.sichtbar === "visible", bewegung.sichtbar);
await ruhig.mouse.move(200, 700, { steps: 10 });
await ruhig.waitForTimeout(400);
await ruhig.mouse.move(1200, 200, { steps: 16 });
await ruhig.waitForTimeout(600);
const ruhigeLage = await ruhig.evaluate(() => {
const b = document.querySelector(".vw-buehne");
const vor = getComputedStyle(b, "::before");
return {
translate: getComputedStyle(b).translate,
reflex: vor.animationName,
reflexSichtbar: parseFloat(vor.opacity),
};
});
pruefe("keine Parallaxe bei reduzierter Bewegung",
ruhigeLage.translate === "none", ruhigeLage.translate);
const ruhigExtra = await ruhig.evaluate(() => {
const be = document.querySelector("#vw-bereich");
const k = document.createElement("div");
k.className = "wd-karte";
be.appendChild(k);
const l = document.querySelector("#vw-bereich .vw-tabs");
return {
kachel: getComputedStyle(k).animationName,
balken: getComputedStyle(l, "::after").transitionDuration,
};
});
pruefe("keine Auftauch-Animation der Kacheln",
ruhigExtra.kachel === "none", ruhigExtra.kachel);
/* Geprueft wird "praktisch sofort", nicht wortwoertlich "0s".
Die Testumgebung emuliert reduzierte Bewegung, indem sie alle
Uebergaenge auf eine Mikrosekunde setzt statt auf null -- gemeldet
wird dann "1e-06s". Wahrnehmbar ist das identisch; ein Test auf
exakt "0s" wuerde nur die Emulation messen, nicht die Regel. */
const laengste = Math.max(...ruhigExtra.balken.split(",")
.map((t) => parseFloat(t) || 0));
pruefe("und der Balken gleitet nicht, sondern springt",
laengste <= 0.01, ruhigExtra.balken);
pruefe("und kein Lichtreflex",
ruhigeLage.reflex === "none" && ruhigeLage.reflexSichtbar === 0,
ruhigeLage.reflex + ", Deckkraft " + ruhigeLage.reflexSichtbar);
await ruhig.close();
console.log("\n===== 6. HANDY =====");
const handy = await browser.newPage({ viewport: { width: 390, height: 844 } });
await handy.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
await handy.waitForTimeout(1300);
await bereichAufbauen(handy, "zahlungen");
const hd = await handy.evaluate(() => {
const st = getComputedStyle(document.querySelector(".vw-buehne"));
const t = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
return { datei: t ? t[1] : "(keins)", groesse: st.backgroundSize };
});
pruefe("Handy: kleine Bildfassung", hd.datei.includes("-klein"), hd.datei);
pruefe("Handy: auch hier kein Zoom", hd.groesse === "cover", hd.groesse);
const ueberlauf = await handy.evaluate(() =>
document.documentElement.scrollWidth - document.documentElement.clientWidth);
pruefe("Handy: nichts laeuft seitlich ueber", ueberlauf <= 1, ueberlauf + "px");
await handy.screenshot({ path: "pruef-vw-schmuck-handy.png" });
await handy.close();
const verpasst = fehlt.filter((f) => /\.(webp|jpg|png|css|js|svg)$/.test(f));
pruefe("keine fehlende Datei", verpasst.length === 0, verpasst.join(", ") || "keine");
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
await browser.close();
srv.close();
process.exit(fehl ? 1 : 0);
});