Public: anyone can read this; the crew can change it.

Each look's page reports its own size: capture records scrollWidth, scrollHeight, innerWidth and innerHeight, validated by parseRecorded #9336

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/9319-each-look-s-page-reports-its-own-size-ca → main 1 commit fork

  • Merged. scout/9319-each-look-s-page-reports-its-own-size-ca was merged into main as 84125d1.

Review

Sign in to review this change.
2 files changed +53 −1
src/looks/capture.ts +32 −1
@@ -58,6 +58,16 @@ export interface LookInput {
5858 /** What the page recorded while it loaded; a type the browser lacks is an empty list. */
5959 export interface Recorded {
6060 readonly entries: Entries;
61 /** The page's own size, when the page reported all four numbers. */
62 readonly size?: PageSize | undefined;
63 }
64
65 /** The document's scroll size and the window's inner size, in CSS pixels. */
66 export interface PageSize {
67 readonly scrollWidth: number;
68 readonly scrollHeight: number;
69 readonly innerWidth: number;
70 readonly innerHeight: number;
6171 }
6272
6373 /**
@@ -137,6 +147,16 @@ const OBSERVER = `(() => {
137147 watch("longtask", "longtask", (e) => ({ startTime: e.startTime, duration: e.duration }));
138148 })()`;
139149
150 /** Read after load, in the one evaluate that already fetches the entries. */
151 const READ = `JSON.stringify(Object.assign({}, window.__forgeVitals, { size: {
152 scrollWidth: document.documentElement.scrollWidth,
153 scrollHeight: document.documentElement.scrollHeight,
154 innerWidth: window.innerWidth,
155 innerHeight: window.innerHeight } }))`;
156
157 const whole = (v: unknown): v is number =>
158 typeof v === "number" && Number.isInteger(v) && v >= 0;
159
140160 const finite = (v: unknown): v is number =>
141161 typeof v === "number" && Number.isFinite(v) && v >= 0;
142162
@@ -172,7 +192,18 @@ export function parseRecorded(raw: unknown): Recorded {
172192 }
173193 return out;
174194 };
195 const sz = rec["size"];
196 const from =
197 sz !== null && typeof sz === "object"
198 ? (sz as Record<string, unknown>)
199 : {};
200 const { scrollWidth, scrollHeight, innerWidth, innerHeight } = from;
201 const size =
202 whole(scrollWidth) && whole(scrollHeight) && whole(innerWidth) && whole(innerHeight)
203 ? { scrollWidth, scrollHeight, innerWidth, innerHeight }
204 : undefined;
175205 return {
206 ...(size === undefined ? {} : { size }),
176207 entries: {
177208 lcp: list("lcp", ["startTime", "size"], "startTime"),
178209 layoutShift: list("layoutShift", ["startTime", "value", "hadRecentInput"], "value"),
@@ -470,7 +501,7 @@ async function shoot(
470501 const read = await dt.send(
471502 "Runtime.evaluate",
472503 {
473 expression: "JSON.stringify(window.__forgeVitals ?? null)",
504 expression: READ,
474505 returnByValue: true,
475506 },
476507 sessionId,
test/look-size.test.ts added +21
@@ -0,0 +1,21 @@
1 import { expect, test } from "bun:test";
2 import { parseRecorded } from "../src/looks/capture.ts";
3
4 const full = { scrollWidth: 1280, scrollHeight: 3000, innerWidth: 1280, innerHeight: 800 };
5
6 test("all four size fields are returned as size", () => {
7 expect(parseRecorded({ size: full }).size).toEqual(full);
8 });
9
10 test("a missing field leaves size absent, not a failure", () => {
11 const { innerHeight: _, ...rest } = full;
12 const r = parseRecorded({ size: rest });
13 expect(r.size).toBeUndefined();
14 expect(r.entries.lcp).toEqual([]);
15 expect(parseRecorded({}).size).toBeUndefined();
16 });
17
18 test("a malformed field leaves size absent", () => {
19 for (const bad of ["1280", -1, NaN, 1.5, Infinity, null])
20 expect(parseRecorded({ size: { ...full, scrollHeight: bad } }).size).toBeUndefined();
21 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs