fix(install-e2e): windows transcripts were empty; player gets #zip= hash + one player per run

Windows transcripts were ZERO bytes: ts-prefix.ps1 formatted with
{0:D2}, but Floor() returns a double and the D specifier is
integer-only - it threw per line, and under the driver's relaxed EAP
every line errored into the void. {0:00} fixes it (custom numeric
format works on doubles). Reproduced the exact pipeline locally
(empty file + Format specifier invalid), verified the fix produces
prefixed merged stdout+stderr with exit code intact. That is also
why the log timeline never auto-synced: there was nothing in the
files to sync.

The GitHub artifact URL 307s to /suites/... server-side and strips
the ?zip= query param. The player now reads the zip URL from a
#zip= HASH param (client-side, survives the redirect) with ?zip=
as fallback; the hash path was verified in a real browser against a
real leg zip (auto-fetch + boot).

Per ethie's design, one player artifact for the whole run: new
leg-player job uploads playback.html (archive:false) before the
matrix legs, the report job needs it, and each ran cell gets TWO
links - 📼 to the player with #zip=<that leg's logs zip> and ⬇️ to
the raw zip. Per-leg player uploads removed from all three run
workflows.
This commit is contained in:
ethernet
2026-08-13 22:51:44 -04:00
parent 08d9f27773
commit cd667debfa
7 changed files with 67 additions and 56 deletions
+19 -4
View File
@@ -269,6 +269,19 @@ async function loadZip(url) {
return true;
}
/**
* Resolve the zip URL: hash param wins (?zip= gets stripped by GitHub's
* artifact redirect, the hash survives), then the query param.
* @returns {string | null}
*/
function resolveZipUrl() {
const m = location.hash.match(/(?:^|[#&])zip=([^&]+)/);
if (m) {
try { return decodeURIComponent(m[1]); } catch { return m[1]; }
}
return new URLSearchParams(location.search).get('zip');
}
/* drag-drop + file picker: the universal transport for auth-gated zips */
const fileInput = document.createElement('input');
fileInput.type = 'file';
@@ -339,13 +352,15 @@ function boot() {
}
async function main() {
const params = new URLSearchParams(location.search);
const zipUrl = params.get('zip');
const zipUrl = resolveZipUrl();
if (zipUrl) {
// Attempt the direct fetch (works when the page is served on
// github.com itself — same-origin, session cookies flow). On failure
// the status line becomes a download link + drop target.
if (await loadZip(zipUrl)) boot();
} else {
// No ?zip=: this is the manual path — the leg player artifact page or
// a local copy. Drop or pick a leg zip.
// No zip param: this is the manual path — the leg player artifact
// page or a local copy. Drop or pick a leg zip.
statusEl.className = 'error';
statusEl.innerHTML =
'Drop a leg artifact zip anywhere on this page (or click here to pick the file).';
+4 -1
View File
@@ -13,6 +13,9 @@ $script:TsPrefixStart = Get-Date
function Add-TsPrefix {
process {
$t = (Get-Date) - $script:TsPrefixStart
"[+{0:D2}:{1:D2}] {2}" -f [math]::Floor($t.TotalMinutes), [math]::Floor($t.TotalSeconds % 60), $_
# {0:00} not {0:D2}: Floor() returns a double and the D specifier
# is integer-only - it throws per line, and under the driver's
# relaxed EAP every line errors into the void (empty transcripts).
"[+{0:00}:{1:00}] {2}" -f [math]::Floor($t.TotalMinutes), [math]::Floor($t.TotalSeconds % 60), $_
}
}