Die Rabattcodeseite war ein Platzhalter -- "Codes folgen in Kuerze" und
darunter eine Vorschaukachel mit dem erfundenen Code "DOGFATHER". Jetzt
steht der erste echte drauf: DOGI10 fuer Van's DIY & Bastelbedarf,
verlinkt auf vans-diy-bastelbedarf.com.
DIE KONDITIONEN SIND ABGELESEN, NICHT GERATEN.
Aus "DOGI10" auf zehn Prozent zu schliessen, waere naheliegend gewesen,
haette zufaellig gestimmt und waere trotzdem falsch gewesen. Nachgesehen
im Shop selbst (src/content/partnercodes.json):
{ "code": "DOGI10", "prozent": 10, "bis": "2026-09-30",
"aktiv": true, "giltAufSale": false }
Zwei der drei Angaben stehen im Namen NICHT drin: dass der Code am
30.09.2026 auslaeuft und dass er auf bereits reduzierte Artikel nicht
gilt. Beides steht jetzt sichtbar auf der Karte. Eine falsche Zusage auf
einer Verkaufsseite kostet VanVan die Diskussion an der Kasse.
Gegengeprueft, dass die Datei auch wirklich gilt und kein Ueberbleibsel
ist: Sie wird an zwei Stellen ausgewertet, src/scripts/cart.ts fuer den
Warenkorb und server/lib/preis-berechnen.js fuer den Endpreis, beide mit
derselben Regel (aktiv !== false UND jetzt <= bis 23:59:59).
DAS ABLAUFDATUM IST EINE ZEITBOMBE, ALSO BEKOMMT ES EINEN ZUENDER.
Ein fest eingetippter Satz "gueltig bis 30.09.2026" stimmt, bis der
Kalender ihn ueberholt -- ab dem 01.10. verspraeche die Seite etwas, das
der Shop schon ablehnt. Genau dieselbe Falle hat am 06.09. den
Oeffnungstest im Shop umgeworfen. Das Datum steht deshalb nicht nur im
Text, sondern einmal als Zahl im Skript: Ist es vorbei, schaltet die
Karte selbsttaetig auf "abgelaufen", streicht den Code durch und sperrt
den Kopierknopf, statt weiter zu werben.
DAS LOGO: AUS EINEM SIEGEL WIRD EINE MUENZE.
Filipe hat das Logo als Bildschirmfoto aus TikTok geliefert, rundes
Siegel auf schwarzem Grund. Ungestellt waere daraus auf der dunklen
Karte ein sichtbarer schwarzer Kasten geworden -- derselbe Fehler wie
bei der Workspace-Marke, deren Zahlen damals tadellos aussahen.
Freigestellt wird per Flutfuellung vom Bildrand (tools/partner-siegel-
freistellen.mjs, 384 px WebP, 37 KB). Eine Kreismaske waere hier sogar
ausrechenbar gewesen -- Mitte 539,5/526,5, Radius 495 -- und haette
genau fuer dieses eine Bild funktioniert. Die Fuellung MISST die Form,
statt sie vorauszusetzen: Der naechste Partner ist ein Eintrag in
AUFTRAEGE und sonst nichts. Die Quelle liegt mit im Repo, sonst laesst
sich das Werkzeug genau einmal ausfuehren und ist danach Dekoration.
"Extrem speziell" fuehrt hier NICHT ueber mehr Farbe -- das Siegel ist
schwarz-weiss, jede Einfaerbung lackierte eine fremde Marke um. Es
fuehrt ueber mehr Material: sechs CSS-Lagen, kein zweites Bild.
Aura weicher Lichthof, atmet in 9 s
Raendel die geriffelte Muenzkante, 72 Zaehne. Sie steht STILL --
eine sich drehende Riffelung flimmert bei 148 px, und das
waere genau die grelle Optik, die die Hausregel ausschliesst
Glanz stattdessen wandert EIN Lichtpunkt in 22 s um die Kante.
Das ist die Bewegung, die eine Muenze im Licht macht
Schliff Praegekante nach innen, oben Licht, unten Schatten
Ablage elliptischer Schatten, damit die Muenze auf der Karte LIEGT
Bei prefers-reduced-motion steht alles davon still.
NEBENBEFUND, DER SONST NIEMANDEM AUFGEFALLEN WAERE: Der Text auf der
Sperrkachel endete auf "sobald die ersten Kooperationen live sind".
Seit heute IST die erste live -- der Satz haette jemanden dafuer zahlen
lassen, auf etwas zu warten, das schon hinter der Sperre liegt.
Ebenfalls nachgemessen statt vermutet: die Warengruppen im
Beschreibungssatz sind die echten Kategorien des Shops.
pruef-rabattcodes EXIT=0 (42 Pruefungen), darunter beide Richtungen der
Supporter-Sperre, die Bildpunkte des ausgelieferten Siegels (Ecken
durchsichtig, Mitte deckend, 69 % Flaeche), das Kopieren gegen die echte
Zwischenablage samt Gegenprobe davor, die Ablaufschaltung mit gestellter
Uhr an beiden Seiten des Stichtags und alle fuenf Sprachen.
Co-Authored-By: Claude Opus 5 <[email protected]>
359 lines
17 KiB
JavaScript
359 lines
17 KiB
JavaScript
/* Prüft die Rabattcode-Seite mit dem ersten echten Partnercode (DOGI10).
|
|
|
|
WAS HIER SCHIEFGEHEN KANN, UND WARUM MAN ES NICHT SIEHT
|
|
|
|
Die Seite hat zwei Zustände, und man bekommt immer nur einen davon zu
|
|
sehen: Ohne Supporter-Abo die Sperre, mit Abo die Codekarte. Wer im
|
|
Browser nachschaut, prüft also die Hälfte. Beide Richtungen stehen
|
|
deshalb hier drin — auch die Gegenprobe, dass die Karte OHNE Abo
|
|
wirklich verschwunden ist und nicht bloss weiter unten steht.
|
|
|
|
Dazu vier Dinge, die im Bild richtig aussehen und trotzdem falsch
|
|
sein können:
|
|
|
|
* Das Siegel. Ein nicht freigestelltes Logo ist auf einer dunklen
|
|
Karte kaum zu erkennen — der schwarze Kasten geht im Hintergrund
|
|
unter. Genau daran ist die Workspace-Marke schon einmal
|
|
vorbeigerutscht ("60,8 % deckend" klang tadellos). Hier werden
|
|
deshalb die Bildpunkte der AUSGELIEFERTEN Datei gemessen: Ecken
|
|
durchsichtig, Mitte deckend.
|
|
|
|
* Der Code. "DOGI10" ist kurz genug, um ihn beim Abtippen zu
|
|
verdrehen, und lang genug, dass es niemandem auffällt. Verglichen
|
|
wird deshalb zeichengenau, nicht "enthält".
|
|
|
|
* Das Ablaufdatum. Es steht an zwei Stellen — sichtbar im Text und
|
|
als Zahl im Skript. Laufen die auseinander, verspricht die Seite
|
|
etwas, das sie selbst schon anders bewertet. Die Prüfung liest
|
|
beide und vergleicht sie.
|
|
|
|
* Die Ablaufschaltung selbst. Sie wird erst am 01.10.2026 zum
|
|
ersten Mal gebraucht — also genau dann, wenn niemand mehr
|
|
hinschaut. Sie wird hier mit gestellter Uhr in beide Richtungen
|
|
gefahren: am letzten gültigen Abend muss der Code GELTEN, am
|
|
Morgen danach nicht mehr. Eine Prüfung, die nur bestätigen kann,
|
|
bestätigt nichts.
|
|
|
|
Läuft rein lokal gegen die Dateien im Arbeitsverzeichnis, rund 20
|
|
Sekunden. Kein Zugriff auf den Server, keine Bestellung, keine Daten.
|
|
|
|
Aufrufen mit: node pruef-rabattcodes.mjs
|
|
*/
|
|
import { chromium } from "playwright";
|
|
import { createServer } from "node:http";
|
|
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
import { join, extname } from "node:path";
|
|
|
|
const W = process.cwd();
|
|
const T = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript",
|
|
".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml",
|
|
".webp": "image/webp", ".ico": "image/x-icon", ".json": "application/json" };
|
|
/* Auf isFile() statt auf "endet nicht mit /" prüfen: main.js fragt auch
|
|
Adressen ohne Schrägstrich am Ende ab, die im Arbeitsverzeichnis ein
|
|
ORDNER sind. readFileSync bricht darauf mit EISDIR ab -- und weil das
|
|
im Serverrückruf passiert, stirbt der ganze Prüflauf statt einer
|
|
einzelnen Anfrage. */
|
|
const srv = createServer((q, r) => {
|
|
const p = join(W, decodeURIComponent(q.url.split("?")[0]));
|
|
if (existsSync(p) && statSync(p).isFile()) {
|
|
r.writeHead(200, { "content-type": T[extname(p)] || "text/plain" });
|
|
return r.end(readFileSync(p));
|
|
}
|
|
r.writeHead(404).end("");
|
|
});
|
|
await new Promise((r) => srv.listen(4331, r));
|
|
const URL = "http://127.0.0.1:4331/rabattcodes.html";
|
|
|
|
/* Erwartungswerte. Sie stammen aus dem Shop, nicht aus dem Codenamen:
|
|
/home/vandiy/vans-diy-bastelbedarf/src/content/partnercodes.json
|
|
{ "code": "DOGI10", "prozent": 10, "bis": "2026-09-30",
|
|
"aktiv": true, "giltAufSale": false }
|
|
Ändert VanVan die Konditionen, muss hier UND auf der Seite nachgezogen
|
|
werden — dass beides zusammen auffällt, ist der Zweck dieser Zeilen. */
|
|
const CODE = "DOGI10";
|
|
const SHOP = "https://vans-diy-bastelbedarf.com/";
|
|
const BIS = "2026-09-30";
|
|
const BIS_TEXT = "30.09.2026";
|
|
|
|
const ergebnisse = [];
|
|
function pruefe(name, ok, zusatz) {
|
|
ergebnisse.push({ name, ok });
|
|
console.log((ok ? " ok " : " FEHL ") + name + (zusatz !== undefined ? " -> " + zusatz : ""));
|
|
}
|
|
|
|
const browser = await chromium.launch();
|
|
|
|
/* Eine Seite im gewünschten Zustand öffnen. `abo` schaltet die Sperre,
|
|
`zeit` stellt die Uhr (nur Date, die Zeitgeber laufen weiter — sonst
|
|
stünde main.js still). */
|
|
async function seiteOeffnen({ abo = true, zeit = null, sprache = "de", viewport, reducedMotion } = {}) {
|
|
const ctx = await browser.newContext({
|
|
viewport: viewport || { width: 1280, height: 1000 },
|
|
permissions: ["clipboard-read", "clipboard-write"],
|
|
reducedMotion,
|
|
});
|
|
const p = await ctx.newPage();
|
|
if (zeit) await p.clock.setFixedTime(zeit);
|
|
await p.addInitScript(([a, s]) => {
|
|
if (a) localStorage.setItem("supporterSubStatus", "active");
|
|
else localStorage.removeItem("supporterSubStatus");
|
|
localStorage.setItem("dogi-sprache", s);
|
|
}, [abo, sprache]);
|
|
await p.goto(URL, { waitUntil: "domcontentloaded" });
|
|
await p.waitForTimeout(700);
|
|
return { ctx, p };
|
|
}
|
|
|
|
/* ===================================================================
|
|
1 — Die Sperre, in BEIDE Richtungen
|
|
=================================================================== */
|
|
console.log("\n=== Supporter-Sperre ===");
|
|
for (const [abo, sichtbar] of [[true, "Karte"], [false, "Sperre"]]) {
|
|
const { ctx, p } = await seiteOeffnen({ abo });
|
|
const z = await p.evaluate(() => ({
|
|
karte: !document.getElementById("rc-inhalt").hidden,
|
|
sperre: !document.getElementById("rc-gesperrt").hidden,
|
|
codeSichtbar: !!document.getElementById("rc-code")?.checkVisibility?.(),
|
|
}));
|
|
const lage = abo ? "Mit Abo" : "Ohne Abo";
|
|
pruefe(`${lage}: ${sichtbar} steht da`,
|
|
z.karte === abo && z.sperre === !abo, `Karte=${z.karte} Sperre=${z.sperre}`);
|
|
/* Gegenprobe: "hidden" ist ein Attribut, kein Beweis. Ohne Abo darf
|
|
der Code auch tatsächlich nicht auf dem Schirm sein. */
|
|
pruefe(`${lage}: Code ist ${abo ? "sichtbar" : "unsichtbar"}`,
|
|
z.codeSichtbar === abo, `sichtbar=${z.codeSichtbar}`);
|
|
await ctx.close();
|
|
}
|
|
|
|
/* ===================================================================
|
|
2 — Das Siegel: geladen, gross genug, wirklich freigestellt
|
|
=================================================================== */
|
|
console.log("\n=== Partnersiegel ===");
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({});
|
|
await p.waitForFunction(() => {
|
|
const i = document.querySelector(".rc-medaillon img");
|
|
return i && i.complete && i.naturalWidth > 0;
|
|
}, null, { timeout: 8000 }).catch(() => {});
|
|
|
|
const b = await p.evaluate(() => {
|
|
const i = document.querySelector(".rc-medaillon img");
|
|
if (!i) return { fehler: "kein Bild" };
|
|
const r = i.getBoundingClientRect();
|
|
const K = 64;
|
|
const c = document.createElement("canvas");
|
|
c.width = K; c.height = K;
|
|
const s = c.getContext("2d", { willReadFrequently: true });
|
|
s.drawImage(i, 0, 0, K, K);
|
|
const d = s.getImageData(0, 0, K, K).data;
|
|
const alpha = (x, y) => d[(y * K + x) * 4 + 3];
|
|
let deckend = 0;
|
|
for (let k = 3; k < d.length; k += 4) if (d[k] > 200) deckend++;
|
|
return {
|
|
quelle: (i.currentSrc || i.src).split("/").pop(),
|
|
natur: i.naturalWidth, breite: Math.round(r.width),
|
|
ecken: [alpha(0, 0), alpha(K - 1, 0), alpha(0, K - 1), alpha(K - 1, K - 1)],
|
|
mitte: alpha(K >> 1, K >> 1),
|
|
anteil: deckend / (K * K),
|
|
};
|
|
});
|
|
|
|
pruefe("Siegel geladen (naturalWidth 384)", b.natur === 384, `${b.quelle} / ${b.natur}px`);
|
|
pruefe("Siegel wird gross genug angezeigt (>= 110 px)", b.breite >= 110, `${b.breite} px`);
|
|
/* DAS ist die Prüfung, die den schwarzen Kasten gefunden hätte. */
|
|
pruefe("Ecken des Siegels sind durchsichtig", b.ecken.every((a) => a <= 20), b.ecken.join("/"));
|
|
pruefe("Mitte des Siegels ist deckend", b.mitte >= 250, String(b.mitte));
|
|
/* Und die Anzahl gehört in die Bedingung, nicht in den Meldetext: ein
|
|
leeres Bild hätte alle drei Prüfungen oben bestanden. Ein Kreis im
|
|
Quadrat deckt pi/4, hier mit 6 % Rand rund 70 %. */
|
|
pruefe("Es ist auch wirklich ein rundes Siegel drauf (60-80 % Fläche)",
|
|
b.anteil > 0.6 && b.anteil < 0.8, `${(b.anteil * 100).toFixed(1)} %`);
|
|
await ctx.close();
|
|
}
|
|
|
|
/* ===================================================================
|
|
3 — Code, Shoplink, Konditionen
|
|
=================================================================== */
|
|
console.log("\n=== Code und Konditionen ===");
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({});
|
|
const z = await p.evaluate(() => {
|
|
const knopf = document.getElementById("rc-code");
|
|
const link = document.querySelector('.rc-partner a.btn-primary');
|
|
return {
|
|
wert: document.querySelector(".rc-code-wert")?.textContent.trim(),
|
|
daten: knopf?.getAttribute("data-code"),
|
|
href: link?.getAttribute("href"),
|
|
ziel: link?.getAttribute("target"),
|
|
rel: link?.getAttribute("rel"),
|
|
fakten: [...document.querySelectorAll(".rc-fakten li")].map((l) => l.textContent.trim()),
|
|
skript: [...document.querySelectorAll("script:not([src])")].map((s) => s.textContent).join("\n"),
|
|
};
|
|
});
|
|
|
|
pruefe(`Angezeigter Code lautet genau „${CODE}“`, z.wert === CODE, z.wert);
|
|
/* Kopiert wird data-code, angezeigt wird der Text. Beide müssen
|
|
dasselbe sagen — sonst kopiert man etwas anderes, als man liest. */
|
|
pruefe("Angezeigter Code und kopierter Code sind identisch", z.daten === z.wert, `${z.daten} / ${z.wert}`);
|
|
pruefe(`Shoplink zeigt genau auf ${SHOP}`, z.href === SHOP, z.href);
|
|
pruefe("Shoplink öffnet in neuem Tab und ohne Rückreferenz",
|
|
z.ziel === "_blank" && (z.rel || "").includes("noopener"), `${z.ziel} / ${z.rel}`);
|
|
pruefe("Drei Konditionen stehen da", z.fakten.length === 3, z.fakten.length + "");
|
|
pruefe("Rabatthöhe genannt (10 %)", z.fakten.some((f) => /10\s?%/.test(f)), z.fakten[0]);
|
|
pruefe(`Ablaufdatum genannt (${BIS_TEXT})`, z.fakten.some((f) => f.includes(BIS_TEXT)), z.fakten[1]);
|
|
pruefe("Sale-Ausnahme genannt", z.fakten.some((f) => /reduziert/i.test(f)), z.fakten[2]);
|
|
|
|
/* Der Text sagt ein Datum, das Skript rechnet mit einem. Zwei Stellen,
|
|
eine Wahrheit — hier wird sie verglichen. */
|
|
const imSkript = (z.skript.match(/var ABLAUF = "([\d-]+)"/) || [])[1];
|
|
pruefe(`Skript rechnet mit demselben Datum wie der Text`,
|
|
imSkript === BIS, `Skript ${imSkript}, Text ${BIS_TEXT}`);
|
|
await ctx.close();
|
|
}
|
|
|
|
/* ===================================================================
|
|
4 — Kopieren, mit Gegenprobe
|
|
=================================================================== */
|
|
console.log("\n=== Kopieren ===");
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({});
|
|
/* Vorher: die Erfolgsmeldung darf NICHT stehen. Ohne diese Zeile
|
|
würde eine dauerhaft sichtbare Meldung als Erfolg durchgehen. */
|
|
const vorher = await p.evaluate(() => !document.getElementById("rc-echo-ok").hidden);
|
|
pruefe("Vor dem Klick steht keine Erfolgsmeldung", vorher === false, String(vorher));
|
|
|
|
await p.click("#rc-code");
|
|
await p.waitForTimeout(400);
|
|
const nachher = await p.evaluate(async () => ({
|
|
meldung: !document.getElementById("rc-echo-ok").hidden,
|
|
fehler: !document.getElementById("rc-echo-fehler").hidden,
|
|
marke: document.getElementById("rc-code").classList.contains("ist-kopiert"),
|
|
zwischenablage: await navigator.clipboard.readText(),
|
|
}));
|
|
pruefe(`In der Zwischenablage steht „${CODE}“`, nachher.zwischenablage === CODE, nachher.zwischenablage);
|
|
pruefe("Erfolgsmeldung erscheint", nachher.meldung === true && nachher.fehler === false,
|
|
`ok=${nachher.meldung} fehler=${nachher.fehler}`);
|
|
pruefe("Knopf zeigt den kopierten Zustand", nachher.marke === true, String(nachher.marke));
|
|
await ctx.close();
|
|
}
|
|
|
|
/* ===================================================================
|
|
5 — Ablauf, in beide Richtungen (gestellte Uhr)
|
|
=================================================================== */
|
|
console.log("\n=== Ablaufschaltung ===");
|
|
for (const [wann, zeit, sollAbgelaufen] of [
|
|
["am letzten gültigen Abend", new Date(`${BIS}T21:30:00`), false],
|
|
["am Morgen danach", new Date("2026-10-01T09:00:00"), true],
|
|
]) {
|
|
const { ctx, p } = await seiteOeffnen({ zeit });
|
|
const z = await p.evaluate(() => ({
|
|
markiert: document.getElementById("rc-partner").classList.contains("ist-abgelaufen"),
|
|
hinweis: !document.getElementById("rc-abgelaufen").hidden,
|
|
gesperrt: document.getElementById("rc-code").disabled,
|
|
}));
|
|
pruefe(`${wann}: Code gilt ${sollAbgelaufen ? "nicht mehr" : "noch"}`,
|
|
z.markiert === sollAbgelaufen && z.hinweis === sollAbgelaufen && z.gesperrt === sollAbgelaufen,
|
|
`markiert=${z.markiert} hinweis=${z.hinweis} gesperrt=${z.gesperrt}`);
|
|
await ctx.close();
|
|
}
|
|
|
|
/* ===================================================================
|
|
6 — Alle fünf Sprachen
|
|
=================================================================== */
|
|
console.log("\n=== Sprachen ===");
|
|
{
|
|
const erwartet = {
|
|
de: ["Dein Code", "Tippen zum Kopieren"],
|
|
"de-CH": ["Din Code", "Tippe zum Kopiere"],
|
|
en: ["Your code", "Tap to copy"],
|
|
fr: ["Ton code", "Touche pour copier"],
|
|
pt: ["O teu código", "Toca para copiar"],
|
|
};
|
|
for (const [sp, [kopf, fuss]] of Object.entries(erwartet)) {
|
|
const { ctx, p } = await seiteOeffnen({ sprache: sp });
|
|
const z = await p.evaluate(() => ({
|
|
kopf: document.querySelector(".rc-code-kopf")?.textContent.trim(),
|
|
fuss: document.querySelector(".rc-code-fuss")?.textContent.trim(),
|
|
/* Ein nicht übersetzter Schlüssel erscheint wörtlich als "rc_..." —
|
|
das ist der sichtbare Rest, nach dem gesucht wird. */
|
|
reste: document.getElementById("rc-inhalt").innerText.match(/rc_[a-z_]+/g) || [],
|
|
entitaeten: document.querySelector(".rc-medaillon img")?.getAttribute("alt") || "",
|
|
}));
|
|
pruefe(`[${sp}] Beschriftungen übersetzt`, z.kopf === kopf && z.fuss === fuss, `${z.kopf} / ${z.fuss}`);
|
|
pruefe(`[${sp}] keine unübersetzten Schlüssel sichtbar`, z.reste.length === 0, z.reste.join(","));
|
|
/* Die Falle aus pruef-texte.mjs, hier für ein Attribut: Wörterbuch
|
|
-> setAttribute kodiert NICHT, "&" stünde wörtlich im Alt-Text
|
|
und würde so vorgelesen. */
|
|
pruefe(`[${sp}] Alt-Text ohne Kodierungsrest`, !/&(amp|lt|gt|quot|#39);/.test(z.entitaeten), z.entitaeten);
|
|
await ctx.close();
|
|
}
|
|
}
|
|
|
|
/* ===================================================================
|
|
7 — Handy, Bewegungsarmut, Bildschirmfotos
|
|
=================================================================== */
|
|
console.log("\n=== Handy und Bewegungsarmut ===");
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({ viewport: { width: 390, height: 844 } });
|
|
const z = await p.evaluate(() => {
|
|
const k = document.getElementById("rc-partner").getBoundingClientRect();
|
|
return {
|
|
quer: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
|
kartenbreite: Math.round(k.width),
|
|
knopfhoehe: Math.round(document.getElementById("rc-code").getBoundingClientRect().height),
|
|
};
|
|
});
|
|
pruefe("Handy 390 px: nichts läuft seitlich über", z.quer <= 0, `${z.quer} px Überhang`);
|
|
pruefe("Handy 390 px: Kopierknopf ist gross genug (>= 44 px)", z.knopfhoehe >= 44, `${z.knopfhoehe} px`);
|
|
await p.screenshot({ path: "pruef-rabattcodes-handy.png", fullPage: true });
|
|
await ctx.close();
|
|
}
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({ reducedMotion: "reduce" });
|
|
const laufen = await p.evaluate(() => {
|
|
const teile = [".rc-medaillon", ".rc-glanz", ".rc-schliff", ".rc-code-wert"];
|
|
return teile.filter((s) => {
|
|
const el = document.querySelector(s);
|
|
return el && getComputedStyle(el).animationName !== "none";
|
|
});
|
|
});
|
|
pruefe("Bei „weniger Bewegung“ steht alles still", laufen.length === 0, laufen.join(","));
|
|
await ctx.close();
|
|
}
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({});
|
|
await p.waitForTimeout(500);
|
|
await p.locator("#rc-partner").screenshot({ path: "pruef-rabattcodes-karte.png" });
|
|
await ctx.close();
|
|
}
|
|
{
|
|
const { ctx, p } = await seiteOeffnen({ abo: false });
|
|
await p.screenshot({ path: "pruef-rabattcodes-gesperrt.png", fullPage: true });
|
|
await ctx.close();
|
|
}
|
|
|
|
await browser.close();
|
|
srv.close();
|
|
|
|
/* ===================================================================
|
|
Ergebnis — mit der ANZAHL in der Bedingung.
|
|
|
|
Ein grüner Lauf mit weniger Prüfungen als vorher ist kein grüner Lauf,
|
|
sondern ein stiller Aussetzer (Erkenntnis 28.08.2026). Die Zahl unten
|
|
wird beim Erweitern mit angehoben.
|
|
=================================================================== */
|
|
const MINDESTENS = 34;
|
|
const fehl = ergebnisse.filter((e) => !e.ok);
|
|
console.log(`\n${ergebnisse.length} Prüfungen, ${fehl.length} fehlgeschlagen.`);
|
|
console.log("Bildschirmfotos: pruef-rabattcodes-karte.png, -handy.png, -gesperrt.png");
|
|
if (ergebnisse.length < MINDESTENS) {
|
|
console.error(`FEHLER: nur ${ergebnisse.length} von mindestens ${MINDESTENS} Prüfungen gelaufen — `
|
|
+ `es ist etwas stillschweigend ausgefallen.`);
|
|
process.exit(1);
|
|
}
|
|
if (fehl.length) {
|
|
console.error("\nFehlgeschlagen:\n" + fehl.map((e) => " - " + e.name).join("\n"));
|
|
process.exit(1);
|
|
}
|
|
console.log("Alles in Ordnung.");
|