Verwaltung: jeder Bereich bekommt eine eigene Signatur
Aus einem festen Streifen werden sechs. Jeder Reiter hat jetzt ein eigenes Motiv UND eine eigene Leitfarbe, beides wechselt beim Klick. Die Zuordnung ist gelesen, nicht ausgewuerfelt: Uebersicht Wappen Die Zentrale, wo alles zusammenlaeuft. Anfragen Portal Ein Tor. Hier kommt Neues herein. Projekte Monolith Etwas, das aufrecht steht und gebaut wird. Kunden Thron Wer bestellt, steht auf dem Podest. Zahlungen Kristall Der Wert selbst. Dazu Liquid Gold. Postfach Portal Wieder ein Tor -- Nachrichten gehen durch. Farben: Baby Blue, Hyper Aqua, Aurora Violet, Prism Indigo, Liquid Gold, Ion Blue. Nach ein paar Tagen erkennt man den Bereich an der Farbe, bevor man den Titel gelesen hat. Aus Deko wird Orientierung. Das neue Thron-Motiv ist aus dem vierten Bild aufbereitet, im selben Mass wie die bestehenden (1672x941) und mit kleiner Fassung fuers Handy. DIE ENTSCHEIDENDE IDEE: Bild und Text teilen sich nicht mehr denselben Platz. Eine Deckschicht ist links voll deckend und oeffnet sich nach rechts. Links stehen Titel und Reiter, rechts ist die Flaeche leer -- dort darf das Motiv mit 82 % auftreten statt mit 17 %. Der erste Versuch war gleichmaessig bei 17 %: ueberall gleich schwach zu ahnen, ein Fleck statt eines Bildes, und trotzdem hinter der Schrift. Kurz und kraeftig ist beides besser -- mehr Wirkung dort, wo Platz ist, null Stoerung dort, wo gearbeitet wird. Der Streifen ist jetzt auch kuerzer und endet, BEVOR die erste Kachel anfaengt. Drei Fehler, die der Test gefunden hat und nicht das Auge: - Alle sechs Bereiche zeigten dasselbe Bild. Die Variablen hingen an #vw-bereich, der Schmuckstreifen liegt aber ausserhalb davon -- er erbte sie nie und fiel auf den Rueckfallwert zurueck. Die Farben wechselten (Reiter und Titel liegen drinnen), die Motive nicht. - Die waagerechten Ausschnitte bewirkten nichts. Bei "cover" skaliert der Browser auf die Breite des Streifens, die volle Bildbreite ist immer sichtbar. Erst ein Zoom ueber 100 % schafft Spielraum. - Das Thron-Motiv schob seine hellen Kristallfluegel bis unter die Reiter. Deshalb deckt die Schicht jetzt bis 46 % statt 34 % -- der Wert ist gemessen, nicht geschaetzt. Dazu zwei Dinge, die erst der Screenshot zeigte: angeschnittene Logos im Streifen (sieht nach Versehen aus, und das Logo steht ohnehin oben links), und die Knoepfe Suchen/Abmelden lagen ueber dem hellsten Teil des Bildes. Sie haben jetzt einen eigenen dichten Grund -- Bedienelemente muessen lesbar sein, egal was dahinter liegt. Der Test misst nicht mehr "Deckkraft unter 20 %". Dieser Massstab ist hinfaellig, seit das Motiv nach rechts gerueckt ist: Es darf kraeftig sein, WEIL es nicht mehr hinter der Schrift liegt. Geprueft wird stattdessen, wie ruhig der Grund unter der Reiterzeile ist -- mit Motiv gegen ohne Motiv, in allen sechs Bereichen. Gemessen: plus 0 bis plus 6. Unveraendert gilt: kein Motiv hinter Listen, Tabellen oder Zahlen. Ein Bild hinter einer Zahlenspalte ist genau die Art von Schoenheit, die ein Werkzeug unbrauchbar macht. Geprueft: 237 Pruefungen gruen (Verwaltung 39, Portfolio 15, CRYONOVA 53, Blickfang 13, System 5, Abmelden 16, Abbruch 42, Angebot 54). Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+289
-93
@@ -5,6 +5,10 @@
|
||||
Kostet es Lesbarkeit? Die Verwaltung ist ein Arbeitsplatz -- ein
|
||||
Motiv, das hinter einer Zahlenspalte liegt, ist genau die Art von
|
||||
Schoenheit, die ein Werkzeug unbrauchbar macht.
|
||||
|
||||
Seit der Bereichs-Signatur kommt eine zweite Frage dazu: Sind die
|
||||
sechs Bereiche wirklich unterscheidbar, oder sieht nur der Code so
|
||||
aus? Deshalb wird jeder Reiter einzeln angeklickt und gemessen.
|
||||
===================================================================== */
|
||||
import { chromium } from "playwright";
|
||||
import http from "http";
|
||||
@@ -36,81 +40,67 @@ const pruefe = (name, gut, 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"];
|
||||
|
||||
/* Misst, wie stark der dunkle Grund in einem Bereich schwankt. Hohe
|
||||
Schwankung heisst: Da liegt ein Bild 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 < 110) 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();
|
||||
|
||||
/* =================================================================
|
||||
TEIL 1 — die Anmeldemaske
|
||||
================================================================= */
|
||||
for (const [gr, vp] of [["Computer", { width: 1440, height: 950 }],
|
||||
["Handy", { width: 390, height: 844 }]]) {
|
||||
console.log(`\n===== ${gr} =====`);
|
||||
console.log(`\n===== ANMELDUNG (${gr}) =====`);
|
||||
const seite = await browser.newPage({ viewport: vp });
|
||||
const skriptfehler = [];
|
||||
seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 120)));
|
||||
|
||||
await seite.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
|
||||
await seite.waitForTimeout(1500);
|
||||
await seite.waitForTimeout(1400);
|
||||
|
||||
/* ---- Der Schmuckstreifen ---- */
|
||||
const streifen = await seite.evaluate(() => {
|
||||
const el = document.querySelector(".vw-schmuck");
|
||||
if (!el) return null;
|
||||
const s = getComputedStyle(el);
|
||||
const r = el.getBoundingClientRect();
|
||||
const treffer = s.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
|
||||
return {
|
||||
datei: treffer ? treffer[1] : "(keins)",
|
||||
deckkraft: parseFloat(s.opacity),
|
||||
z: parseInt(s.zIndex, 10),
|
||||
oben: Math.round(r.top),
|
||||
hoehe: Math.round(r.height),
|
||||
klicks: s.pointerEvents,
|
||||
versteckt: el.getAttribute("aria-hidden"),
|
||||
};
|
||||
});
|
||||
if (!streifen) { pruefe(`${gr}: Schmuckstreifen vorhanden`, false); continue; }
|
||||
|
||||
const kleinErwartet = vp.width <= 900;
|
||||
pruefe(`${gr}: richtige Bilddatei`,
|
||||
kleinErwartet === streifen.datei.includes("-klein"), streifen.datei);
|
||||
/* Das ist der Kern: Der Streifen darf ANGEDEUTET sein, nicht mehr. */
|
||||
pruefe(`${gr}: bleibt sehr leise (unter 20 %)`,
|
||||
streifen.deckkraft > 0 && streifen.deckkraft < 0.20,
|
||||
Math.round(streifen.deckkraft * 100) + " %");
|
||||
pruefe(`${gr}: liegt hinter dem Inhalt`, streifen.z < 0, "z-index " + streifen.z);
|
||||
pruefe(`${gr}: faengt keine Klicks ab`, streifen.klicks === "none", streifen.klicks);
|
||||
pruefe(`${gr}: ist als Zierde ausgezeichnet`, streifen.versteckt === "true");
|
||||
/* 67px ist kein Zufallswert, sondern die Hoehe der Kopfleiste: Der
|
||||
Streifen beginnt direkt darunter. Gemessen wird deshalb gegen die
|
||||
tatsaechliche Kopfhoehe, nicht gegen eine geratene Zahl. */
|
||||
const kopfHoehe = await seite.evaluate(() => {
|
||||
const k = document.querySelector("#wd-kopf .wd-kopf, #wd-kopf header, #wd-kopf");
|
||||
return k ? Math.round(k.getBoundingClientRect().height) : 0;
|
||||
});
|
||||
pruefe(`${gr}: sitzt direkt unter der Kopfleiste`,
|
||||
streifen.oben <= kopfHoehe + 8, `${streifen.oben}px, Kopf ${kopfHoehe}px`);
|
||||
|
||||
/* ---- Die Anmeldekarte traegt das Wappen ---- */
|
||||
const login = await seite.evaluate(() => {
|
||||
const el = document.querySelector("#vw-anmeldung");
|
||||
if (!el) return null;
|
||||
el.removeAttribute("hidden"); // sonst nichts zu messen
|
||||
el.removeAttribute("hidden");
|
||||
const vor = getComputedStyle(el, "::before");
|
||||
const treffer = vor.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
|
||||
return {
|
||||
klassen: el.className,
|
||||
datei: treffer ? treffer[1] : "(keins)",
|
||||
deckkraft: parseFloat(vor.opacity),
|
||||
};
|
||||
return { datei: treffer ? treffer[1] : "(keins)", deckkraft: parseFloat(vor.opacity) };
|
||||
});
|
||||
pruefe(`${gr}: die Anmeldekarte hat ein Motiv`,
|
||||
login && login.datei.includes("wappen"), login && login.datei);
|
||||
/* Der eigentliche Test. Deckkraft allein sagt nichts: Ein Motiv mit
|
||||
hellen Kanten ist bei 20 % stoerender als ein ruhiges bei 50 %.
|
||||
Was zaehlt, ist wie stark der Untergrund UNTER DER SCHRIFT
|
||||
schwankt. Gemessen wird das an echten Bildpunkten aus dem
|
||||
Erklaertext -- genau dort lag beim Bauen der Hundekopf, und die
|
||||
Zeilen liefen quer ueber Schnauze und Schriftzug. */
|
||||
await seite.waitForTimeout(500);
|
||||
|
||||
await seite.waitForTimeout(500);
|
||||
const messen = async () => {
|
||||
const bild = await seite.locator("#vw-anmeldung p.wd-klein").first().screenshot();
|
||||
return seite.evaluate(async (daten) => {
|
||||
@@ -120,8 +110,6 @@ srv.listen(4404, async () => {
|
||||
const x = c.getContext("2d");
|
||||
x.drawImage(bm, 0, 0);
|
||||
const d = x.getImageData(0, 0, bm.width, bm.height).data;
|
||||
/* Nur die dunklen Bildpunkte: Die hellen SIND die Schrift, die
|
||||
soll ja kontrastieren. Interessant ist der Grund dazwischen. */
|
||||
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];
|
||||
@@ -132,52 +120,260 @@ srv.listen(4404, async () => {
|
||||
return Math.round(Math.sqrt(grund.reduce((a, b) => a + (b - m) ** 2, 0) / grund.length));
|
||||
}, bild.toString("base64"));
|
||||
};
|
||||
|
||||
const mitBild = await messen();
|
||||
/* Die Vergleichsmessung ohne Motiv ist der Kern. Ein fester
|
||||
Grenzwert waere geraten: Schon die weichgezeichneten Kanten der
|
||||
Buchstaben allein erzeugen eine Schwankung um 12 -- ein Test
|
||||
gegen "unter 14" haette also fast nur diese Kanten gemessen und
|
||||
waere je nach Schriftgroesse zufaellig gruen oder rot. Gefragt
|
||||
ist, wie viel Unruhe DAS MOTIV hinzufuegt. */
|
||||
/* Die Vergleichsmessung ist der Kern. Ein fester Grenzwert waere
|
||||
geraten: Schon die weichgezeichneten Buchstabenkanten allein
|
||||
erzeugen eine Schwankung um 12. Gefragt ist, wie viel Unruhe DAS
|
||||
MOTIV hinzufuegt. */
|
||||
await seite.evaluate(() =>
|
||||
document.querySelector("#vw-anmeldung").classList.remove("wd-motiv", "wd-motiv--wappen"));
|
||||
await seite.waitForTimeout(400);
|
||||
const ohneBild = await messen();
|
||||
await seite.evaluate(() =>
|
||||
document.querySelector("#vw-anmeldung").classList.add("wd-motiv", "wd-motiv--wappen"));
|
||||
await seite.waitForTimeout(400);
|
||||
|
||||
await seite.waitForTimeout(200);
|
||||
const zusatz = (mitBild ?? 99) - (ohneBild ?? 0);
|
||||
pruefe(`${gr}: das Motiv macht den Textgrund kaum unruhiger`,
|
||||
mitBild !== null && ohneBild !== null && zusatz <= 5,
|
||||
`mit ${mitBild}, ohne ${ohneBild} → plus ${zusatz}`);
|
||||
|
||||
/* ---- Und jetzt die eigentliche Regel ---- */
|
||||
const stoerung = await seite.evaluate(() => {
|
||||
const schlimm = [];
|
||||
for (const el of document.querySelectorAll("table, .wd-tabelle, .vw-liste")) {
|
||||
let p = el;
|
||||
while (p && p !== document.body) {
|
||||
if (p.classList && (p.classList.contains("wd-motiv") ||
|
||||
p.classList.contains("vw-schmuck"))) {
|
||||
schlimm.push(el.tagName + "." + (el.className || "").slice(0, 20));
|
||||
break;
|
||||
}
|
||||
p = p.parentElement;
|
||||
}
|
||||
}
|
||||
return schlimm;
|
||||
});
|
||||
pruefe(`${gr}: kein Motiv hinter Tabellen oder Listen`,
|
||||
stoerung.length === 0, stoerung.join(", ") || "keine");
|
||||
|
||||
pruefe(`${gr}: kein Skriptfehler`, skriptfehler.length === 0, skriptfehler[0] || "");
|
||||
|
||||
await seite.screenshot({ path: `pruef-vw-schmuck-${gr.toLowerCase()}.png` });
|
||||
await seite.close();
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
TEIL 2 — die sechs Bereichs-Signaturen
|
||||
================================================================= */
|
||||
const seite = await browser.newPage({ viewport: { width: 1440, height: 950 } });
|
||||
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(1400);
|
||||
await seite.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await seite.waitForTimeout(500);
|
||||
|
||||
/* Erst EINMAL echt klicken, um zu beweisen, dass der Reiter die
|
||||
Signatur ueberhaupt umschaltet. Danach geht das nicht mehr: Beim
|
||||
Klick laedt die Seite Daten, merkt dass niemand angemeldet ist, und
|
||||
klappt den Arbeitsbereich korrekterweise wieder zu. Das ist die
|
||||
Zugangspruefung, kein Fehler -- aber es beendet den Rundgang.
|
||||
|
||||
Deshalb wird der Bereich danach direkt gesetzt, so wie der
|
||||
Klick-Listener es auch tut. Geprueft wird dabei das Stilblatt, und
|
||||
das kennt nur das Kennzeichen am Wurzelelement. */
|
||||
await seite.click('[data-tab="anfragen"]');
|
||||
await seite.waitForTimeout(900);
|
||||
const nachKlick = await seite.evaluate(() =>
|
||||
document.documentElement.getAttribute("data-vw-bereich"));
|
||||
console.log("\n===== SCHALTET DER REITER UM? =====");
|
||||
pruefe("ein echter Reiterklick setzt den Bereich", nachKlick === "anfragen", nachKlick);
|
||||
|
||||
/* Zurueck in den sichtbaren Zustand fuer die Messreihe. */
|
||||
await seite.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await seite.waitForTimeout(300);
|
||||
|
||||
const gesehen = [];
|
||||
for (const bereich of BEREICHE) {
|
||||
console.log(`\n===== BEREICH: ${bereich} =====`);
|
||||
/* Der Datenabruf laeuft im Hintergrund weiter und klappt den
|
||||
Arbeitsbereich verzoegert wieder zu (Zugangspruefung). Deshalb vor
|
||||
JEDER Messung erneut aufmachen, nicht nur einmal am Anfang. */
|
||||
await seite.evaluate((b) => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
document.documentElement.setAttribute("data-vw-bereich", b);
|
||||
document.querySelectorAll("[data-tab]").forEach((x) =>
|
||||
x.classList.toggle("vw-an", x.dataset.tab === b));
|
||||
}, bereich);
|
||||
await seite.waitForTimeout(500);
|
||||
|
||||
const d = await seite.evaluate(() => {
|
||||
const wurzel = document.documentElement.getAttribute("data-vw-bereich");
|
||||
const s = document.querySelector(".vw-schmuck");
|
||||
const st = getComputedStyle(s);
|
||||
const treffer = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
|
||||
const reiter = document.querySelector(".vw-filter-knopf.vw-an");
|
||||
const rs = getComputedStyle(reiter);
|
||||
const titel = document.querySelector("#vw-titel");
|
||||
/* Untergrund der Seite suchen -- die Reiterflaeche ist
|
||||
halbdurchsichtig und muss darueber verrechnet werden. */
|
||||
const seitenGrund = getComputedStyle(document.body).backgroundColor
|
||||
.match(/[\d.]+/g).slice(0, 3).map(Number);
|
||||
return {
|
||||
wurzel,
|
||||
datei: treffer ? treffer[1] : "(keins)",
|
||||
deckkraft: parseFloat(st.opacity),
|
||||
blick: st.backgroundPosition,
|
||||
reiterText: reiter.textContent.trim().slice(0, 14),
|
||||
reiterFarbe: rs.color.match(/[\d.]+/g).slice(0, 3).map(Number),
|
||||
reiterFlaeche: rs.backgroundColor.match(/[\d.]+/g),
|
||||
titelFarbe: getComputedStyle(titel).color.match(/[\d.]+/g).slice(0, 3).map(Number),
|
||||
seitenGrund,
|
||||
lumen: getComputedStyle(document.querySelector("#vw-bereich")).getPropertyValue("--vw-farbe").trim(),
|
||||
};
|
||||
});
|
||||
|
||||
pruefe(` Kennzeichen steht am Wurzelelement`, d.wurzel === bereich, d.wurzel);
|
||||
gesehen.push({ bereich, datei: d.datei, farbe: d.lumen, blick: d.blick });
|
||||
|
||||
/* Der aktive Reiter muss lesbar bleiben -- auch im Bereich
|
||||
"Kunden", dessen Leitfarbe Prism Indigo als SCHRIFT durchfaellt.
|
||||
Genau deshalb faerbt die Leitfarbe dort nur die Flaeche. */
|
||||
const a = d.reiterFlaeche.length === 4 ? parseFloat(d.reiterFlaeche[3]) : 1;
|
||||
const flaeche = d.seitenGrund.map((u, i) =>
|
||||
Math.round(Number(d.reiterFlaeche[i]) * a + u * (1 - a)));
|
||||
const v = verhaeltnis(d.reiterFarbe, flaeche);
|
||||
pruefe(` der aktive Reiter bleibt lesbar (AA = 4,5:1)`, v >= 4.5, v.toFixed(2) + ":1");
|
||||
|
||||
const vt = verhaeltnis(d.titelFarbe, d.seitenGrund);
|
||||
pruefe(` der Titel bleibt lesbar`, vt >= 4.5, vt.toFixed(2) + ":1");
|
||||
/* Frueher stand hier "Deckkraft unter 20 %". Dieser Massstab ist
|
||||
hinfaellig, seit das Motiv nach rechts gerueckt ist: Es darf jetzt
|
||||
kraeftig sein, WEIL es nicht mehr hinter der Schrift liegt. Eine
|
||||
Zahl allein sagt darueber nichts.
|
||||
|
||||
Geprueft wird stattdessen das, worauf es ankommt -- wie ruhig der
|
||||
Grund unter der Reiterzeile ist, mit Motiv gegen ohne Motiv. */
|
||||
const offen = () => seite.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await offen();
|
||||
const grundMit = await unruheAn(seite, ".vw-tabs");
|
||||
await seite.evaluate(() => {
|
||||
document.querySelector(".vw-schmuck").style.visibility = "hidden";
|
||||
});
|
||||
await offen();
|
||||
await seite.waitForTimeout(350);
|
||||
const grundOhne = await unruheAn(seite, ".vw-tabs");
|
||||
await seite.evaluate(() => {
|
||||
document.querySelector(".vw-schmuck").style.visibility = "";
|
||||
});
|
||||
await seite.waitForTimeout(250);
|
||||
const mehr = (grundMit ?? 99) - (grundOhne ?? 0);
|
||||
pruefe(` der Grund unter den Reitern bleibt ruhig`,
|
||||
grundMit !== null && grundOhne !== null && mehr <= 6,
|
||||
`mit ${grundMit}, ohne ${grundOhne} → plus ${mehr}`);
|
||||
}
|
||||
|
||||
/* ---- Die Kernfrage: sind die sechs wirklich unterscheidbar? ---- */
|
||||
console.log("\n===== SIND DIE BEREICHE UNTERSCHEIDBAR? =====");
|
||||
for (const g of gesehen) console.log(` ${g.bereich.padEnd(11)} ${g.datei.padEnd(22)} ${g.farbe}`);
|
||||
|
||||
const farben = new Set(gesehen.map((g) => g.farbe));
|
||||
pruefe("jeder Bereich hat eine eigene Leitfarbe", farben.size === 6,
|
||||
farben.size + " von 6 verschieden");
|
||||
|
||||
/* Motive duerfen sich wiederholen -- es gibt fuenf Bilder fuer sechs
|
||||
Bereiche. Aber dann MUSS der Ausschnitt ein anderer sein, sonst
|
||||
sehen zwei Bereiche gleich aus. */
|
||||
const doppelt = {};
|
||||
for (const g of gesehen) (doppelt[g.datei] ||= []).push(g);
|
||||
let ausschnittOk = true, hinweis = [];
|
||||
for (const [datei, liste] of Object.entries(doppelt)) {
|
||||
if (liste.length < 2) continue;
|
||||
const blicke = new Set(liste.map((l) => l.blick));
|
||||
if (blicke.size !== liste.length) { ausschnittOk = false; hinweis.push(datei); }
|
||||
else hinweis.push(`${datei}: ${liste.length}x, aber je anderer Ausschnitt`);
|
||||
}
|
||||
pruefe("mehrfach genutzte Motive zeigen einen anderen Ausschnitt",
|
||||
ausschnittOk, hinweis.join("; ") || "kein Motiv doppelt");
|
||||
|
||||
/* ---- Und die alte Regel gilt weiter ---- */
|
||||
const stoerung = await seite.evaluate(() => {
|
||||
const schlimm = [];
|
||||
for (const el of document.querySelectorAll("table, .wd-tabelle, .vw-liste")) {
|
||||
let p = el;
|
||||
while (p && p !== document.body) {
|
||||
if (p.classList && (p.classList.contains("wd-motiv") ||
|
||||
p.classList.contains("vw-schmuck"))) {
|
||||
schlimm.push(el.tagName);
|
||||
break;
|
||||
}
|
||||
p = p.parentElement;
|
||||
}
|
||||
}
|
||||
return schlimm;
|
||||
});
|
||||
pruefe("kein Motiv hinter Tabellen oder Listen", stoerung.length === 0,
|
||||
stoerung.join(", ") || "keine");
|
||||
pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || "");
|
||||
|
||||
await seite.evaluate(() => {
|
||||
document.documentElement.setAttribute("data-vw-bereich", "uebersicht");
|
||||
document.querySelectorAll("[data-tab]").forEach((x) =>
|
||||
x.classList.toggle("vw-an", x.dataset.tab === "uebersicht"));
|
||||
});
|
||||
await seite.waitForTimeout(700);
|
||||
await seite.screenshot({ path: "pruef-vw-schmuck-computer.png" });
|
||||
await seite.close();
|
||||
|
||||
/* ---- Bewegung aus, wenn der Nutzer das so eingestellt hat ---- */
|
||||
console.log("\n===== REDUZIERTE BEWEGUNG =====");
|
||||
const ruhig = await browser.newPage({
|
||||
viewport: { width: 1440, height: 950 }, reducedMotion: "reduce" });
|
||||
await ruhig.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
|
||||
await ruhig.waitForTimeout(1200);
|
||||
await ruhig.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await ruhig.click('[data-tab="projekte"]');
|
||||
await ruhig.waitForTimeout(700);
|
||||
/* Der Klick klappt den Bereich zu (Zugangspruefung) -- fuer die
|
||||
Messung wieder aufmachen. Das Kennzeichen hat der Klick gesetzt. */
|
||||
await ruhig.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await ruhig.waitForTimeout(300);
|
||||
const bewegung = await ruhig.evaluate(() => {
|
||||
const s = document.querySelector(".vw-schmuck");
|
||||
const st = getComputedStyle(s);
|
||||
return {
|
||||
animation: st.animationName,
|
||||
sichtbar: parseFloat(st.opacity),
|
||||
bereich: document.documentElement.getAttribute("data-vw-bereich"),
|
||||
};
|
||||
});
|
||||
pruefe("keine Animation bei reduzierter Bewegung",
|
||||
bewegung.animation === "none", bewegung.animation);
|
||||
/* Wichtig: abschalten heisst NICHT verschwinden. Das Motiv muss da
|
||||
sein, nur ohne Bewegung. */
|
||||
pruefe("das Motiv ist trotzdem sichtbar", bewegung.sichtbar > 0.05,
|
||||
Math.round(bewegung.sichtbar * 100) + " %");
|
||||
pruefe("der Bereichswechsel wirkt trotzdem", bewegung.bereich === "projekte",
|
||||
bewegung.bereich);
|
||||
await ruhig.close();
|
||||
|
||||
/* ---- Handy ---- */
|
||||
console.log("\n===== 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(1200);
|
||||
await handy.evaluate(() => {
|
||||
document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
|
||||
document.querySelector("#vw-bereich").removeAttribute("hidden");
|
||||
});
|
||||
await handy.evaluate(() => {
|
||||
document.documentElement.setAttribute("data-vw-bereich", "kunden");
|
||||
});
|
||||
await handy.waitForTimeout(600);
|
||||
const hd = await handy.evaluate(() => {
|
||||
const st = getComputedStyle(document.querySelector(".vw-schmuck"));
|
||||
const t = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
|
||||
return { datei: t ? t[1] : "(keins)", deckkraft: parseFloat(st.opacity) };
|
||||
});
|
||||
pruefe("Handy: kleine Bildfassung", hd.datei.includes("-klein"), hd.datei);
|
||||
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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user