Die Stelle bleibt -- auch wenn man ueber die Kopfzeile zurueckgeht
Filipe, zum vierten Mal: "muss ich jedes mal wenn ich von einer seite
zurück scrolle immer wieder runter scrollen um weiter zu schauen und
das nervt."
DIE WIEDERHERSTELLUNG GAB ES SEIT DEM 20.09. -- sorgfaeltig gebaut,
mit Warten auf die nachgeladene Hoehe, mit Aufgeben nach drei
Sekunden, mit "wer selbst scrollt, gewinnt".
SIE LIEF NUR FAST NIE. Denn sie lief ausschliesslich bei
`navigation.type === "back_forward"`, also beim Zurueck-Knopf des
Browsers. Im Haus geht man anders zurueck: ueber die Kopfzeile ("TEAM
DOGI › HIGHLIGHTS"), und dort steht ein ganz gewoehnliches
`<a href="start.html">`. Fuer den Browser ist das eine Fahrt
VORWAERTS. Die Bedingung war also falsch -- und der else-Zweig
loeschte die eben gemerkte Stelle noch dazu.
IM KOPF DERSELBEN DATEI STAND DIE LOESUNG ALS ABSICHT: "Ein eigener
Merker faengt den Fall ab, dass der Zurueck-Knopf der Kopfleiste
benutzt wurde." Gebaut wurde er nie.
UND DESHALB HAT ES IN JEDEM TEST FUNKTIONIERT: Wer mit `goBack()`
prueft, prueft den einen Weg, den niemand geht. Gemessen am 21.09.:
ueber goBack sauber wiederhergestellt, ueber den Link in der
Kopfzeile oben gelandet. Gefunden hat es Filipes Bildschirmvideo --
17 Sekunden, drei Bilder, und der Ablauf war eindeutig.
Der Merker gibt es jetzt wirklich: Beim Klick auf einen EIGENEN
Zurueck-Weg (`.zurueck`, `.zurueck-knopf`) wird das Ziel notiert; wer
dort binnen 30 Sekunden ankommt, bekommt seine Stelle zurueck. Er
gilt fuer genau eine Ankunft -- wer die Seite eine Minute spaeter
normal oeffnet, faengt wieder oben an. Und NUR fuer diese zwei
Klassen, nicht fuer jeden Link: Sonst landete man beim Oeffnen eines
Brettes mitten darin.
Dazu der Rueckwaerts-Speicher des Browsers: Holt er eine Seite aus dem
Speicher, laeuft keine Zeile mehr, und unser `manual` verbietet ihm
gleichzeitig, die Stelle selbst wiederherzustellen. Ein `pageshow`
faengt das ab. Dieser Fall ist NICHT gemessen -- in Playwright greift
der Speicher nicht -- sondern aus der Spezifikation begruendet. Das
steht auch so im Kommentar, damit niemand spaeter glaubt, er sei
nachgewiesen.
NEU: pruef-stelle.mjs, 9 Pruefungen. Sie geht beide Wege zurueck --
Kopfzeile UND Browser -- und haelt ausdruecklich fest, dass der erste
KEIN "back_forward" ist. Dazu die Gegenprobe, dass eine frisch
geoeffnete Seite oben anfaengt: Ohne sie waere alles auch dann gruen,
wenn die Seite IMMER zur alten Stelle springt, und das waere
schlimmer als das Problem.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/* =====================================================================
|
||||
pruef-stelle.mjs — Bleibt man, wo man war? (21.09.2026)
|
||||
|
||||
Filipe, zum vierten Mal: "muss ich jedes mal wenn ich von einer seite
|
||||
zurück scrolle immer wieder runter scrollen um weiter zu schauen und
|
||||
das nervt, wieso bleibt es nicht sofort da wo ich dran war anstatt
|
||||
dass ich immer wieder ganz runter scrollen muss."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM ES DREIMAL ALS BEHOBEN GALT UND NIE BEHOBEN WAR
|
||||
|
||||
Die Wiederherstellung in kopf.js gibt es seit dem 20.09. Sie ist
|
||||
sorgfaeltig gebaut -- sie merkt sich die Stelle je Adresse, wartet,
|
||||
bis die nachgeladene Seite hoch genug ist, und hoert auf, sobald der
|
||||
Mensch selbst scrollt.
|
||||
|
||||
Sie lief nur fast nie. Denn sie lief NUR bei
|
||||
`navigation.type === "back_forward"`, also beim Zurueck-Knopf des
|
||||
Browsers. Im Haus geht man aber anders zurueck: ueber die Kopfzeile,
|
||||
und dort steht ein ganz normales `<a href="start.html">`. Fuer den
|
||||
Browser ist das eine Fahrt VORWAERTS. Die Bedingung war falsch, und
|
||||
der `else`-Zweig loeschte die eben gemerkte Stelle sogar noch.
|
||||
|
||||
IM KOPF DERSELBEN DATEI STAND DIE LOESUNG ALS ABSICHT: "Ein eigener
|
||||
Merker faengt den Fall ab, dass der Zurueck-Knopf der Kopfleiste
|
||||
benutzt wurde." Gebaut wurde er nie. Ein Kommentar, der eine Absicht
|
||||
beschreibt, erfuellt sie nicht.
|
||||
|
||||
UND DESHALB HAT ES IN JEDEM TEST FUNKTIONIERT: Wer mit
|
||||
`page.goBack()` prueft, prueft den einen Weg, den niemand geht.
|
||||
Gemessen am 21.09.: ueber `goBack()` sauber wiederhergestellt,
|
||||
ueber den Link in der Kopfzeile oben gelandet. Man muss den Weg
|
||||
gehen, den der Mensch geht.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WAS HIER GEMESSEN WIRD
|
||||
|
||||
1. Ueber den Link in der Kopfzeile zurueck -> Stelle bleibt
|
||||
2. Ueber den Zurueck-Knopf des Browsers -> Stelle bleibt
|
||||
3. GEGENPROBE: Eine Seite frisch oeffnen -> faengt oben an
|
||||
|
||||
Die dritte ist nicht Zierde. Ohne sie waere die Pruefung auch dann
|
||||
gruen, wenn die Seite IMMER an die alte Stelle springt -- und das
|
||||
waere schlimmer als das Problem: Wer eine Kachel antippt, will oben
|
||||
anfangen.
|
||||
|
||||
Aufrufen mit: node server/pruef-stelle.mjs
|
||||
===================================================================== */
|
||||
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { request as httpAnfrage } from "node:http";
|
||||
import { eigenerPort } from "./helfer-port.mjs";
|
||||
|
||||
const PORT = await eigenerPort(import.meta, "pruef-stelle");
|
||||
const ordner = mkdtempSync(join(tmpdir(), "ws-stelle-"));
|
||||
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
||||
process.env.PORT = String(PORT);
|
||||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||||
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
||||
|
||||
const express = (await import("express")).default;
|
||||
const ec = express.response.cookie;
|
||||
express.response.cookie = function (n, w, o) {
|
||||
return ec.call(this, n, w, { ...(o || {}), secure: false });
|
||||
};
|
||||
import { notbremse } from "./helfer-notbremse.mjs";
|
||||
await import("./index.js");
|
||||
notbremse(180_000, "pruef-stelle");
|
||||
await new Promise((r) => setTimeout(r, 900));
|
||||
|
||||
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
||||
|
||||
let fehler = 0, geprueft = 0;
|
||||
const melde = (t) => console.log(t);
|
||||
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||||
|
||||
const CREW = "crew.dogfather-universe.com";
|
||||
|
||||
function roh(pfad, koerper) {
|
||||
return new Promise((fertig, schief) => {
|
||||
const a = httpAnfrage({
|
||||
host: "127.0.0.1", port: PORT, path: pfad, method: koerper ? "POST" : "GET",
|
||||
headers: { Host: CREW, ...(koerper ? { "Content-Type": "application/json" } : {}) },
|
||||
}, (antwort) => {
|
||||
let text = "";
|
||||
antwort.on("data", (s) => { text += s; });
|
||||
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
||||
});
|
||||
a.on("error", schief);
|
||||
if (koerper) a.write(JSON.stringify(koerper));
|
||||
a.end();
|
||||
});
|
||||
}
|
||||
|
||||
const { DatabaseSync } = await import("node:sqlite");
|
||||
const { scryptSync, randomBytes } = await import("node:crypto");
|
||||
{
|
||||
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
||||
const salz = randomBytes(16).toString("hex");
|
||||
const hash = scryptSync("CODE-DOGI-0001", salz, 64,
|
||||
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
||||
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)"
|
||||
+ " VALUES (?,?,?,?,?,1,?)")
|
||||
.run("Filipe", "admin", hash, salz, 32768, new Date().toISOString());
|
||||
d.close();
|
||||
}
|
||||
|
||||
let an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001" });
|
||||
if (an.code === 400 && an.text.includes("alter_offen")) {
|
||||
an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001", alter_ok: true });
|
||||
}
|
||||
const keks = [].concat(an.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
||||
ok(!!keks, "angemeldet");
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
let pw = null;
|
||||
try {
|
||||
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
||||
} catch { /* dritter Ausgang */ }
|
||||
|
||||
if (!pw) {
|
||||
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
|
||||
melde(" Das ist kein Befund, aber auch kein bestandener Test.");
|
||||
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
|
||||
process.exit(fehler ? 1 : 0);
|
||||
}
|
||||
|
||||
const browser = await pw.chromium.launch();
|
||||
|
||||
/** Scrollt auf der Startseite nach unten, oeffnet ein Brett und kommt
|
||||
* auf dem uebergebenen Weg zurueck. Gibt zurueck, wo man landet. */
|
||||
async function hinUndZurueck(wieZurueck) {
|
||||
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
||||
const [name, ...rest] = z.split("=");
|
||||
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
||||
}));
|
||||
const seite = await ctx.newPage();
|
||||
try {
|
||||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(1700);
|
||||
const hoch = await seite.evaluate(() => document.documentElement.scrollHeight);
|
||||
/* DIE HOEHE GEHOERT IN DIE BEDINGUNG. Auf einer Seite, die kuerzer
|
||||
ist als das Fenster, kann man gar nicht scrollen -- dann misst
|
||||
alles Folgende nichts. */
|
||||
if (hoch < 1600) return { zuKurz: hoch };
|
||||
await seite.evaluate(() => window.scrollTo(0, 1200));
|
||||
await seite.waitForTimeout(400);
|
||||
const vor = await seite.evaluate(() => Math.round(window.scrollY));
|
||||
|
||||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=highlight`,
|
||||
{ waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(1400);
|
||||
|
||||
if (wieZurueck === "link") {
|
||||
/* Ueber die Kopfzeile -- der Weg, den Filipe im Video geht.
|
||||
`evaluate` statt `click`, weil der Link je nach Breite hinter
|
||||
dem Knopf liegen kann; das Ereignis ist dasselbe. */
|
||||
const gabEs = await seite.evaluate(() => {
|
||||
const a = document.querySelector("a.zurueck");
|
||||
if (!a) return false;
|
||||
a.click();
|
||||
return true;
|
||||
});
|
||||
if (!gabEs) return { keinLink: true };
|
||||
await seite.waitForNavigation({ waitUntil: "networkidle" }).catch(() => {});
|
||||
} else {
|
||||
await seite.goBack({ waitUntil: "load" });
|
||||
}
|
||||
await seite.waitForTimeout(3000);
|
||||
const nach = await seite.evaluate(() => ({
|
||||
y: Math.round(window.scrollY),
|
||||
art: performance.getEntriesByType("navigation")[0]?.type,
|
||||
wo: location.pathname,
|
||||
}));
|
||||
return { vor, ...nach };
|
||||
} finally {
|
||||
await ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
melde("\n=== 1. Zurueck ueber die Kopfzeile ===");
|
||||
{
|
||||
const r = await hinUndZurueck("link");
|
||||
ok(!r.zuKurz && !r.keinLink,
|
||||
r.zuKurz ? `die Startseite ist nur ${r.zuKurz}px hoch -- nichts zu messen`
|
||||
: r.keinLink ? "es gibt keinen Zurueck-Link in der Kopfzeile"
|
||||
: "der Weg ueber die Kopfzeile ist da");
|
||||
if (!r.zuKurz && !r.keinLink) {
|
||||
ok(r.wo === "/workspace/start.html", `man landet auf der Startseite (${r.wo})`);
|
||||
/* DASS ES EBEN KEIN "back_forward" IST, ist der Kern der Sache --
|
||||
und wird deshalb ausdruecklich festgehalten. Waere es eines,
|
||||
pruefte dieser Abschnitt denselben Weg wie der naechste. */
|
||||
ok(r.art !== "back_forward",
|
||||
`und zwar als gewoehnliche Fahrt vorwaerts (${r.art}) -- genau der Fall, `
|
||||
+ "der bisher nicht wiederhergestellt wurde");
|
||||
ok(r.y >= r.vor - 80,
|
||||
`die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
|
||||
}
|
||||
}
|
||||
|
||||
melde("\n=== 2. Zurueck ueber den Browser ===");
|
||||
{
|
||||
const r = await hinUndZurueck("back");
|
||||
ok(!r.zuKurz, r.zuKurz ? `zu kurz (${r.zuKurz}px)` : "die Seite ist hoch genug");
|
||||
if (!r.zuKurz) {
|
||||
ok(r.art === "back_forward", `der Browser meldet ein Zurueck (${r.art})`);
|
||||
ok(r.y >= r.vor - 80, `die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
|
||||
}
|
||||
}
|
||||
|
||||
melde("\n=== 3. Gegenprobe: frisch geoeffnet faengt oben an ===");
|
||||
{
|
||||
/* Ohne diese Zeile waere alles oben auch dann gruen, wenn die
|
||||
Seite IMMER an die alte Stelle springt. Das waere schlimmer als
|
||||
das Problem: Wer eine Kachel antippt, will oben anfangen. */
|
||||
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
||||
const [name, ...rest] = z.split("=");
|
||||
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
||||
}));
|
||||
const seite = await ctx.newPage();
|
||||
try {
|
||||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(1500);
|
||||
await seite.evaluate(() => window.scrollTo(0, 1200));
|
||||
await seite.waitForTimeout(400);
|
||||
/* Neu aufrufen, ohne ueber einen Zurueck-Weg zu kommen. */
|
||||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(2600);
|
||||
const y = await seite.evaluate(() => Math.round(window.scrollY));
|
||||
ok(y < 80, `frisch geoeffnet steht die Seite oben (${y}px)`);
|
||||
} finally {
|
||||
await ctx.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
|
||||
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
||||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
||||
process.exit(fehler ? 1 : 0);
|
||||
Reference in New Issue
Block a user