Say the right thing beside a case that did not run

A blocked case was showing the last claim it managed to check -- 'and the app
knows it is a child profile' -- rather than the reason it stopped, which is the
one thing the card exists to tell the owner. The message now follows the
outcome: a blocked case is asking him for something, so it shows the request; a
failed one is asking about the app, so it shows the claim that did not hold.

Screenshots also needed a file token. e2e_runs is superuser-only, which makes its
files protected, and an <img> tag sends no Authorization header -- so without one
every picture on the page is a broken icon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-09-10 01:38:41 +02:00
parent 17d3be8892
commit 679ab38424

View file

@ -715,12 +715,22 @@ function normName(s){
return String(s).toLowerCase().replace(/\.[a-z0-9]+$/, "").replace(/[^a-z0-9]+/g, "_"); return String(s).toLowerCase().replace(/\.[a-z0-9]+$/, "").replace(/[^a-z0-9]+/g, "_");
} }
// A short-lived token for the protected files, fetched once per render.
//
// `e2e_runs` is superuser-only, which makes its files protected, which means an
// <img> tag cannot fetch one on the strength of the page's own session -- the
// browser sends no Authorization header with an image request. Without this
// every screenshot on the page is a broken icon.
var fileTok = "";
function shotUrl(r, caseId, shotName){ function shotUrl(r, caseId, shotName){
var want = normName(caseId + "_" + shotName); var want = normName(caseId + "_" + shotName);
var hit = (r.shots || []).filter(function(f){ var hit = (r.shots || []).filter(function(f){
return normName(f).indexOf(want) === 0; return normName(f).indexOf(want) === 0;
})[0]; })[0];
return hit ? "/api/files/e2e_runs/" + r.id + "/" + hit : null; if (!hit) return null;
return "/api/files/e2e_runs/" + r.id + "/" + hit +
(fileTok ? "?token=" + encodeURIComponent(fileTok) : "");
} }
function caseRow(r, c){ function caseRow(r, c){
@ -730,10 +740,17 @@ function caseRow(r, c){
return u ? '<a href="' + u + '" target="_blank"><img loading="lazy" src="' + u + return u ? '<a href="' + u + '" target="_blank"><img loading="lazy" src="' + u +
'"><div class="cap">' + esc(s.name) + "</div></a>" : ""; '"><div class="cap">' + esc(s.name) + "</div></a>" : "";
}).join(""); }).join("");
// What to say beside the id, in the order that answers the question the
// outcome raises. A blocked case is asking the owner for something, so its
// message is that request; a failed one is asking about the app, so it is the
// claim that did not hold. Reading the last claim for everything showed a
// blocked case saying "and the app knows it is a child profile", which is the
// last thing it managed to check and not the reason it stopped.
var checks = (c.evidence || []).filter(function(e){ return e.kind === "checked"; }); var checks = (c.evidence || []).filter(function(e){ return e.kind === "checked"; });
var claim = checks.length var broke = checks.filter(function(e){ return e.ok === false; })[0];
? checks[checks.length - 1].claim var claim = c.outcome === "blocked" ? (c.needs || "")
: (c.needs || c.error || ""); : c.outcome === "fail" ? ((broke && broke.claim) || c.error || "")
: (checks.length ? checks[checks.length - 1].claim : "");
return '<div class="case">' + outcomePill(c.outcome) + return '<div class="case">' + outcomePill(c.outcome) +
'<span class="cid">' + esc(c.id) + "</span>" + '<span class="cid">' + esc(c.id) + "</span>" +
(c.blocker ? '<span class="pill un">blokuje</span>' : "") + (c.blocker ? '<span class="pill un">blokuje</span>' : "") +
@ -1351,6 +1368,11 @@ async function load(){
if (prefs.view === "e2e") { if (prefs.view === "e2e") {
var rs = await records("e2e_runs", { sort: "-created", perPage: 40 }) var rs = await records("e2e_runs", { sort: "-created", perPage: 40 })
.catch(function(){ return { items: [] }; }); .catch(function(){ return { items: [] }; });
fileTok = "";
try {
var ft = await api("/api/files/token", { method: "POST" });
fileTok = ft.token || "";
} catch (_) { /* the cards still render; the pictures will not */ }
el("main").innerHTML = viewE2e(rs.items || []); el("main").innerHTML = viewE2e(rs.items || []);
wire(); wire();
return; return;