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
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9319-each-look-s-page-reports-its-own-size-ca → main 1 commit fork
2 files
changed +53
−1
src/looks/capture.ts +32 −1
| @@ -58,6 +58,16 @@ export interface LookInput { | ||
| 58 | 58 | /** What the page recorded while it loaded; a type the browser lacks is an empty list. */ |
| 59 | 59 | export interface Recorded { |
| 60 | 60 | 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; | |
| 61 | 71 | } |
| 62 | 72 | |
| 63 | 73 | /** |
| @@ -137,6 +147,16 @@ const OBSERVER = `(() => { | ||
| 137 | 147 | watch("longtask", "longtask", (e) => ({ startTime: e.startTime, duration: e.duration })); |
| 138 | 148 | })()`; |
| 139 | 149 | |
| 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 | ||
| 140 | 160 | const finite = (v: unknown): v is number => |
| 141 | 161 | typeof v === "number" && Number.isFinite(v) && v >= 0; |
| 142 | 162 | |
| @@ -172,7 +192,18 @@ export function parseRecorded(raw: unknown): Recorded { | ||
| 172 | 192 | } |
| 173 | 193 | return out; |
| 174 | 194 | }; |
| 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; | |
| 175 | 205 | return { |
| 206 | ...(size === undefined ? {} : { size }), | |
| 176 | 207 | entries: { |
| 177 | 208 | lcp: list("lcp", ["startTime", "size"], "startTime"), |
| 178 | 209 | layoutShift: list("layoutShift", ["startTime", "value", "hadRecentInput"], "value"), |
| @@ -470,7 +501,7 @@ async function shoot( | ||
| 470 | 501 | const read = await dt.send( |
| 471 | 502 | "Runtime.evaluate", |
| 472 | 503 | { |
| 473 | expression: "JSON.stringify(window.__forgeVitals ?? null)", | |
| 504 | expression: READ, | |
| 474 | 505 | returnByValue: true, |
| 475 | 506 | }, |
| 476 | 507 | 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 | }); |
new above
offline