webperf-snippets 0.1.0 → 0.3.0

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.
Files changed (69) hide show
  1. package/README.md +185 -20
  2. package/package.json +1 -1
  3. package/snippets/CoreWebVitals/CLS.js +29 -15
  4. package/snippets/CoreWebVitals/INP.js +14 -7
  5. package/snippets/CoreWebVitals/LCP-Image-Entropy.js +29 -11
  6. package/snippets/CoreWebVitals/LCP-Subparts.js +4 -0
  7. package/snippets/CoreWebVitals/LCP-Trail.js +14 -3
  8. package/snippets/CoreWebVitals/LCP-Video-Candidate.js +20 -2
  9. package/snippets/CoreWebVitals/LCP.js +4 -2
  10. package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js +9 -2
  11. package/snippets/Interaction/DOM-Size-and-Depth.js +134 -0
  12. package/snippets/Interaction/Forced-Synchronous-Layout.js +33 -7
  13. package/snippets/Interaction/Input-Latency-Breakdown.js +45 -1
  14. package/snippets/Interaction/Interactions.js +16 -5
  15. package/snippets/Interaction/Layout-Shift-Loading-and-Interaction.js +44 -13
  16. package/snippets/Interaction/Long-Animation-Frames-Helpers.js +28 -1
  17. package/snippets/Interaction/Long-Animation-Frames-Script-Attribution.js +8 -0
  18. package/snippets/Interaction/Long-Animation-Frames.js +31 -2
  19. package/snippets/Interaction/LongTask.js +37 -5
  20. package/snippets/Interaction/Scroll-Performance.js +40 -0
  21. package/snippets/Interaction/WORKFLOWS.md +9 -0
  22. package/snippets/Loading/Back-Forward-Cache.js +174 -381
  23. package/snippets/Loading/CSS-Media-Queries-Analysis.js +76 -44
  24. package/snippets/Loading/Cache-Strategy-Analysis.js +52 -30
  25. package/snippets/Loading/Compression-Audit.js +172 -0
  26. package/snippets/Loading/Content-Visibility.js +8 -3
  27. package/snippets/Loading/Critical-CSS-Detection.js +8 -6
  28. package/snippets/Loading/Event-Processing-Time.js +15 -2
  29. package/snippets/Loading/FCP.js +39 -5
  30. package/snippets/Loading/Find-Above-The-Fold-Lazy-Loaded-Images.js +1 -1
  31. package/snippets/Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js +7 -5
  32. package/snippets/Loading/Find-render-blocking-resources.js +11 -13
  33. package/snippets/Loading/First-And-Third-Party-Script-Info.js +40 -20
  34. package/snippets/Loading/First-And-Third-Party-Script-Timings.js +21 -13
  35. package/snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js +35 -4
  36. package/snippets/Loading/Inline-CSS-Info-and-Size.js +8 -6
  37. package/snippets/Loading/Inline-Script-Info-and-Size.js +14 -7
  38. package/snippets/Loading/JS-Execution-Time-Breakdown.js +23 -10
  39. package/snippets/Loading/Priority-Hints-Audit.js +4 -1
  40. package/snippets/Loading/Resource-Hints-Validation.js +15 -0
  41. package/snippets/Loading/Resource-Hints.js +29 -8
  42. package/snippets/Loading/SSR-Hydration-Data-Analysis.js +77 -8
  43. package/snippets/Loading/Script-Loading.js +55 -25
  44. package/snippets/Loading/Server-Timing-Early-Hints.js +126 -0
  45. package/snippets/Loading/Service-Worker-Analysis.js +229 -53
  46. package/snippets/Loading/Speculation-Rules-Inspector.js +134 -0
  47. package/snippets/Loading/TTFB-Resources.js +59 -18
  48. package/snippets/Loading/TTFB-Sub-Parts.js +9 -2
  49. package/snippets/Loading/TTFB.js +3 -2
  50. package/snippets/Loading/Third-Party-Impact-by-Domain.js +216 -0
  51. package/snippets/Loading/Validate-Preload-Async-Defer-Scripts.js +21 -3
  52. package/snippets/Loading/WORKFLOWS.md +47 -1
  53. package/snippets/Loading/Webfont-Usage-Analyzer.js +332 -0
  54. package/snippets/Media/Image-Element-Audit.js +22 -3
  55. package/snippets/Media/Oversized-Images.js +217 -0
  56. package/snippets/Media/SVG-Embedded-Bitmap-Analysis.js +18 -3
  57. package/snippets/Media/WORKFLOWS.md +22 -0
  58. package/snippets/Resources/Network-Bandwidth-Connection-Quality.js +7 -0
  59. package/snippets/SCHEMA.md +1176 -0
  60. package/snippets/_shared/helpers.js +44 -0
  61. package/src/bin.js +86 -57
  62. package/src/crux.js +84 -0
  63. package/src/decision-tree.js +11 -1
  64. package/src/interactions.js +30 -0
  65. package/src/report-to.js +46 -0
  66. package/src/reporters/human.js +20 -1
  67. package/src/reporters/markdown.js +103 -0
  68. package/src/runner.js +81 -17
  69. package/src/workflows/loading.js +14 -0
package/src/runner.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { chromium } from "playwright";
2
+ import { loadSnippet } from "./load-snippet.js";
3
+ import { runInteractions } from "./interactions.js";
2
4
 
3
5
  export const VIEWPORT_PRESETS = {
4
6
  mobile: { width: 375, height: 812 },
@@ -15,6 +17,27 @@ function toExpression(source) {
15
17
  return source.trim().replace(/;\s*$/, "");
16
18
  }
17
19
 
20
+ async function evaluateItems(page, items) {
21
+ const results = [];
22
+ for (const item of items) {
23
+ try {
24
+ const result = await page.evaluate(toExpression(item.source));
25
+ if (result && typeof result === "object") {
26
+ results.push({ id: item.id, ...result });
27
+ } else {
28
+ results.push({
29
+ id: item.id,
30
+ status: "error",
31
+ error: "Snippet did not return an object — check the IIFE return statement",
32
+ });
33
+ }
34
+ } catch (err) {
35
+ results.push({ id: item.id, status: "error", error: err.message });
36
+ }
37
+ }
38
+ return results;
39
+ }
40
+
18
41
  export async function runSnippets({
19
42
  url,
20
43
  items,
@@ -22,12 +45,42 @@ export async function runSnippets({
22
45
  headless = true,
23
46
  viewport = VIEWPORT_PRESETS.mobile,
24
47
  navTimeout = DEFAULT_NAV_TIMEOUT,
48
+ interactScript,
49
+ storageState,
25
50
  }) {
26
51
  const browser = await chromium.launch({ headless });
27
52
  try {
28
- const context = await browser.newContext({ viewport });
53
+ const context = await browser.newContext({ viewport, storageState });
29
54
  const page = await context.newPage();
55
+ const pageErrors = [];
56
+ page.on("pageerror", (err) => pageErrors.push(err.message));
57
+ const navStart = Date.now();
58
+ await page.goto(url, { waitUntil: "load", timeout: navTimeout });
59
+ if (waitMs > 0) await page.waitForTimeout(waitMs);
60
+ const navMs = Date.now() - navStart;
61
+ if (interactScript) await runInteractions(page, interactScript);
62
+ const results = await evaluateItems(page, items);
63
+ return { url, navMs, results, pageErrors };
64
+ } finally {
65
+ await browser.close();
66
+ }
67
+ }
30
68
 
69
+ export async function runMeasurement({
70
+ url,
71
+ workflow,
72
+ rules = [],
73
+ waitMs = 3000,
74
+ headless = true,
75
+ viewport = VIEWPORT_PRESETS.mobile,
76
+ navTimeout = DEFAULT_NAV_TIMEOUT,
77
+ interactScript,
78
+ storageState,
79
+ }) {
80
+ const browser = await chromium.launch({ headless });
81
+ try {
82
+ const context = await browser.newContext({ viewport, storageState });
83
+ const page = await context.newPage();
31
84
  const pageErrors = [];
32
85
  page.on("pageerror", (err) => pageErrors.push(err.message));
33
86
 
@@ -35,26 +88,37 @@ export async function runSnippets({
35
88
  await page.goto(url, { waitUntil: "load", timeout: navTimeout });
36
89
  if (waitMs > 0) await page.waitForTimeout(waitMs);
37
90
  const navMs = Date.now() - navStart;
91
+ if (interactScript) await runInteractions(page, interactScript);
92
+
93
+ const items = workflow.steps.map((step) => ({
94
+ id: step.id,
95
+ path: step.path,
96
+ source: loadSnippet(step.path),
97
+ }));
98
+ const initialResults = await evaluateItems(page, items);
38
99
 
39
- const results = [];
40
- for (const item of items) {
41
- try {
42
- const result = await page.evaluate(toExpression(item.source));
43
- if (result && typeof result === "object") {
44
- results.push({ id: item.id, ...result });
45
- } else {
46
- results.push({
47
- id: item.id,
48
- status: "error",
49
- error: "Snippet did not return an object — check the IIFE return statement",
50
- });
51
- }
52
- } catch (err) {
53
- results.push({ id: item.id, status: "error", error: err.message });
100
+ const followUps = [];
101
+ for (const result of initialResults) {
102
+ for (const rule of rules) {
103
+ if (rule.when(result)) followUps.push({ ...rule.append, reason: rule.reason });
54
104
  }
55
105
  }
56
106
 
57
- return { url, navMs, results, pageErrors };
107
+ let followUpResults = [];
108
+ if (followUps.length > 0) {
109
+ const followItems = followUps.map((f) => ({
110
+ id: f.id,
111
+ path: f.path,
112
+ source: loadSnippet(f.path),
113
+ }));
114
+ const raw = await evaluateItems(page, followItems);
115
+ followUpResults = raw.map((r) => {
116
+ const f = followUps.find((x) => x.id === r.id);
117
+ return f ? { ...r, reason: f.reason } : r;
118
+ });
119
+ }
120
+
121
+ return { url, navMs, results: [...initialResults, ...followUpResults], pageErrors };
58
122
  } finally {
59
123
  await browser.close();
60
124
  }
@@ -0,0 +1,14 @@
1
+ // Loading performance workflow — timing-sensitive measurements requiring a real page load.
2
+ // Complements the audit workflow (deterministic structural checks) with live metrics.
3
+ export const loadingWorkflow = {
4
+ id: "loading",
5
+ title: "Loading Performance",
6
+ steps: [
7
+ { id: "TTFB", path: "Loading/TTFB" },
8
+ { id: "FCP", path: "Loading/FCP" },
9
+ { id: "render-blocking", path: "Loading/Find-render-blocking-resources" },
10
+ { id: "resource-hints", path: "Loading/Resource-Hints-Validation" },
11
+ { id: "scripts", path: "Loading/Script-Loading" },
12
+ { id: "fonts", path: "Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold" },
13
+ ],
14
+ };