Skip to content

Commit 978dc56

Browse files
committed
Self-host Playwright's trace viewer next to the runs
trace.playwright.dev is HTTPS, so when the runs viewer is reached over plain HTTP (tailscale IP) the browser blocks its fetch of trace.zip as mixed content and the trace never loads. playwright-core ships the trace viewer as a static PWA — rebuild-viewer.ts now copies it to runs/trace-viewer and the run page links there same-origin, which also keeps traces viewable offline.
1 parent 96a9e92 commit 978dc56

2 files changed

Lines changed: 32 additions & 6 deletions

File tree

e2e/scripts/rebuild-viewer.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
// test: refresh runs/manifest.json + vite-build the SPA into runs/.
33
// Usage: bun e2e/scripts/rebuild-viewer.ts
44
import { execFileSync } from "node:child_process";
5-
import { rmSync } from "node:fs";
6-
import { join } from "node:path";
5+
import { cpSync, rmSync } from "node:fs";
6+
import { createRequire } from "node:module";
7+
import { dirname, join } from "node:path";
78
import { fileURLToPath } from "node:url";
89

910
import { buildManifest } from "../src/viewer/manifest";
@@ -17,4 +18,23 @@ execFileSync("bunx", ["vite", "build", "--config", "viewer/vite.config.ts"], {
1718
cwd: e2eDir,
1819
stdio: "inherit",
1920
});
21+
22+
// Self-host Playwright's trace viewer (a static PWA shipped inside
23+
// playwright-core) next to the runs. trace.playwright.dev is HTTPS and
24+
// browsers refuse to let it fetch a trace.zip from a plain-HTTP server
25+
// (mixed content) — which is exactly how this viewer is reached over
26+
// tailscale. Same-origin, the restriction disappears.
27+
// playwright-core isn't a direct dependency — resolve it through
28+
// playwright (which is), then walk from its entry to the package root.
29+
const require = createRequire(import.meta.url);
30+
const playwrightCoreEntry = createRequire(
31+
require.resolve("playwright"),
32+
).resolve("playwright-core");
33+
const traceViewerSrc = join(
34+
dirname(playwrightCoreEntry),
35+
"lib/vite/traceViewer",
36+
);
37+
rmSync(join(runsDir, "trace-viewer"), { recursive: true, force: true });
38+
cpSync(traceViewerSrc, join(runsDir, "trace-viewer"), { recursive: true });
39+
2040
console.log(`viewer rebuilt at ${runsDir}`);

e2e/viewer/src/App.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ const Matrix = () => {
151151

152152
// ---------------------------------------------------------------------------
153153
// Run page: status + error + artifacts. The trace opens in Playwright's own
154-
// viewer (trace.playwright.dev fetches the zip from this server, client-side).
154+
// viewer, SELF-HOSTED at /trace-viewer (copied out of playwright-core by
155+
// rebuild-viewer.ts). trace.playwright.dev would work on localhost but not
156+
// over tailscale: it's HTTPS, this server is HTTP, and browsers block the
157+
// mixed-content fetch of trace.zip. Same-origin avoids all of it.
155158
// ---------------------------------------------------------------------------
156159

157160
// The suite's motel (local OTLP store, booted by the global setup on a
@@ -204,9 +207,12 @@ const RunView = ({ target, slug }: { target: string; slug: string }) => {
204207
const film = has("film.mp4") ? "film.mp4" : null;
205208
const cast = has("terminal.cast") ? "terminal.cast" : null;
206209
const traceUrl = has("trace.zip")
207-
? `https://trace.playwright.dev/?trace=${encodeURIComponent(
208-
new URL(`${base}/trace.zip`, window.location.href).toString(),
209-
)}`
210+
? new URL(
211+
`trace-viewer/index.html?trace=${encodeURIComponent(
212+
new URL(`${base}/trace.zip`, window.location.href).toString(),
213+
)}`,
214+
window.location.href,
215+
).toString()
210216
: null;
211217
// The live two-recording player needs both recordings AND a focus
212218
// timeline whose clocks are anchored. Anything less falls back to

0 commit comments

Comments
 (0)