webperf-snippets 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/interactions.js +27 -0
- package/src/runner.js +5 -3
- package/src/tracking.js +4 -4
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ npx webperf-snippets https://web.dev --snippet Interaction/Interactions --intera
|
|
|
77
77
|
|
|
78
78
|
The Interaction snippets (`Interactions`, `Input-Latency-Breakdown`, `LongTask`, `Forced-Synchronous-Layout`...) are installed before the first step and read after the last one, so their result holds what the steps caused.
|
|
79
79
|
|
|
80
|
+
The CLI waits for the browser to report the timing of the last interaction before it reads the snippets. A script that ends right after a click still reads the page before anything the click sets in motion later has happened, such as a request or an animation, so end it with a `wait` step when you want to measure that.
|
|
81
|
+
|
|
80
82
|
Interaction audit (long tasks, long animation frames, scroll, layout shifts, forced layouts, interaction latency). It scrolls the page, or runs your `--interact-script`; the steps that need a click are skipped when the script has none:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -132,7 +134,7 @@ Without `--report-to` the CLI makes no external calls. With it, the results are
|
|
|
132
134
|
"timestamp": "2026-05-06T10:00:00.000Z",
|
|
133
135
|
"navMs": 1240,
|
|
134
136
|
"results": [],
|
|
135
|
-
"meta": { "viewport": "mobile", "waitMs": 3000, "cli_version": "0.4.
|
|
137
|
+
"meta": { "viewport": "mobile", "waitMs": 3000, "cli_version": "0.4.1" }
|
|
136
138
|
}
|
|
137
139
|
```
|
|
138
140
|
|
package/package.json
CHANGED
package/src/interactions.js
CHANGED
|
@@ -38,3 +38,30 @@ export async function runInteractions(page, source) {
|
|
|
38
38
|
await executeStep(page, step);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
// The browser reports the timing of an event after the frame that follows it, and an observer gets it
|
|
43
|
+
// in a task after that frame. Reading a snippet right after the last step can miss that step, so this
|
|
44
|
+
// waits for two frames and a task, which takes a few milliseconds on a page that renders. A page that
|
|
45
|
+
// never renders a frame stops the wait at `limitMs`, and a page that is gone is not an error here: the
|
|
46
|
+
// evaluation that follows reports it.
|
|
47
|
+
export async function settle(page, limitMs = 1000) {
|
|
48
|
+
try {
|
|
49
|
+
await page.evaluate(
|
|
50
|
+
(limit) =>
|
|
51
|
+
new Promise((resolve) => {
|
|
52
|
+
const timer = setTimeout(resolve, limit);
|
|
53
|
+
requestAnimationFrame(() =>
|
|
54
|
+
requestAnimationFrame(() =>
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
resolve();
|
|
58
|
+
}, 0)
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
}),
|
|
62
|
+
limitMs
|
|
63
|
+
);
|
|
64
|
+
} catch {
|
|
65
|
+
// the page navigated or closed
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chromium } from "playwright";
|
|
2
2
|
import { loadSnippet } from "./load-snippet.js";
|
|
3
|
-
import { runInteractions, loadInteractionSteps, isInputStep } from "./interactions.js";
|
|
3
|
+
import { runInteractions, loadInteractionSteps, isInputStep, settle } from "./interactions.js";
|
|
4
4
|
import { isTrackingSnippet } from "./tracking.js";
|
|
5
5
|
import { nextSteps } from "./decision-tree.js";
|
|
6
6
|
|
|
@@ -42,14 +42,16 @@ async function evaluateItems(page, items) {
|
|
|
42
42
|
|
|
43
43
|
// Runs the items around a set of interactions. A tracking snippet is installed first, so it sees
|
|
44
44
|
// the interactions, and answers through its getDataFn afterwards. The other items run after the
|
|
45
|
-
// interactions, as before.
|
|
46
|
-
//
|
|
45
|
+
// interactions, as before. The browser reports the timing of an interaction after the frame that
|
|
46
|
+
// follows it, so the runner lets it settle before it reads anything. Results keep the order of the
|
|
47
|
+
// items. Without interactions every item runs once, as it always did.
|
|
47
48
|
async function evaluateAroundInteractions(page, items, interactions) {
|
|
48
49
|
if (!interactions) return evaluateItems(page, items);
|
|
49
50
|
|
|
50
51
|
const tracking = items.filter((item) => isTrackingSnippet(item.path ?? "", item.source));
|
|
51
52
|
const installed = await evaluateItems(page, tracking);
|
|
52
53
|
await runInteractions(page, interactions);
|
|
54
|
+
await settle(page);
|
|
53
55
|
const others = await evaluateItems(
|
|
54
56
|
page,
|
|
55
57
|
items.filter((item) => !tracking.includes(item))
|
package/src/tracking.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// A tracking snippet installs observers and answers later through a window function named in
|
|
2
|
-
//
|
|
3
|
-
// getDataFn but
|
|
1
|
+
// A tracking snippet installs observers and answers later through a window function named in its
|
|
2
|
+
// result (getDataFn). The Interaction snippets work this way, and so does INP, whose value only
|
|
3
|
+
// comes out of getINP(). CLS also names a getDataFn but answers with its value in one go.
|
|
4
4
|
export function isTrackingSnippet(path, source) {
|
|
5
|
-
return path.startsWith("Interaction/") && source.includes("getDataFn");
|
|
5
|
+
return (path.startsWith("Interaction/") || path === "CoreWebVitals/INP") && source.includes("getDataFn");
|
|
6
6
|
}
|