Vier Dinge dazu, drei davon Bewegung, eines eine Reparatur. 1. DAS LICHT FOLGT WIEDER DEM ZEIGER Es war nie weg. --wd-lichtx/--wd-lichty wurden die ganze Zeit gesetzt, der Lichtkegel stand korrekt an der richtigen Stelle -- er war nur unsichtbar geworden. Die 28 % Deckkraft aus dem Grundsystem sind fuer eine dunkle, undurchsichtige Kachel gedacht. Auf einer Glasflaeche, durch die eine beleuchtete Kristallwelt schimmert, geht das schlicht unter. Jetzt wirkt es auf zwei Ebenen: der Lichtkegel auf der Flaeche, und die KANTE der Kachel leuchtet dort auf, wo der Zeiger steht. Zusammen sieht es aus, als laege eine echte Lichtquelle ueber dem Glas, statt als waere ein Fleck aufgemalt. Die Farbe ist die Leitfarbe des Bereichs -- im Kundenbereich leuchtet es Indigo, bei den Zahlungen Gold. 2. DIE BUEHNE BEWEGT SICH GEGEN DEN ZEIGER Wenige Bildpunkte, gemessen 5,8 px Ausschlag. Gerade genug, dass sich der Raum echt anfuehlt statt wie eine Tapete -- und wenig genug, dass beim Lesen nichts im Augenwinkel wandert. Ein Test haelt die Obergrenze fest. 3. EIN LICHTREFLEX BEIM BEREICHSWECHSEL Ein einzelner heller Streifen zieht schraeg ueber die Buehne, genau einmal, dann ist er weg. Ein Moment, kein Dauerflackern. 4. DIE KACHEL HEBT SICH BEIM UEBERFAHREN AN Zwei Bildpunkte. Sie soll reagieren, nicht huepfen. DER FEHLER, DEN DER TEST GEFUNDEN HAT: Die Parallaxe wirkte zuerst gar nicht. Die Werte kamen sauber an (--vw-px, --vw-py standen korrekt am Element), das Bild stand trotzdem still. Grund: Die Einblend-Animation animiert "transform" und haelt ihren Endwert fest (fill-mode both) -- und eine Animation schlaegt jede normale Regel. Die Verschiebung steht deshalb jetzt in "translate", einer eigenen Eigenschaft, die VOR "transform" angewendet wird. Beide koennen sich so nicht mehr in die Quere kommen. Alles Bewegte ist bei prefers-reduced-motion aus: keine Parallaxe, kein Reflex, kein Anheben. Die Buehne bleibt aber sichtbar -- abschalten heisst nicht verschwinden. Auch das wird geprueft. Die Parallaxe laeuft nur auf Geraeten mit echtem Zeiger und ist ueber requestAnimationFrame gedrosselt. Ohne die Drosselung rechnet der Browser bei jeder einzelnen Zeigerbewegung neu, und das merkt man ausgerechnet beim Scrollen durch lange Listen. Geprueft: 236 Pruefungen gruen (Verwaltung 38, Portfolio 15, CRYONOVA 53, Blickfang 13, System 5, Abmelden 16, Abbruch 42, Angebot 54). Die Lesbarkeit ueber der Buehne liegt weiter bei 11,9 bis 14,6:1, verlangt sind 4,5:1. Co-Authored-By: Claude Opus 5 <[email protected]>
378 lines
16 KiB
JavaScript
378 lines
16 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===== 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);
|
|
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);
|
|
});
|