sitespeed.io 36.2.5 → 36.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
+
1
2
  # CHANGELOG - sitespeed.io (we use [semantic versioning](https://semver.org))
2
3
 
4
+ ## 36.4.0 - 2025-02-14
5
+ ### Added
6
+ * Crux update: Get fractions for RTT [#4436](https://github.com/sitespeedio/sitespeed.io/pull/4436), that extra LCP information [#4437](https://github.com/sitespeedio/sitespeed.io/pull/4437) and make sure LCP resource type fraction is sent to TSDB [#4438](https://github.com/sitespeedio/sitespeed.io/pull/4438).
7
+
8
+ ## 36.3.0 - 2025-02-08
9
+ ### Added
10
+ * Chrome 133 and Firefox 135 in the Docker container [#4431](https://github.com/sitespeedio/sitespeed.io/pull/4431).
11
+ * Browsertime 24.2.0 with Chromedriver 133 [#4432](https://github.com/sitespeedio/sitespeed.io/pull/4432).
12
+
13
+ ### Fixed
14
+ * Extra guard against missing HARs [#4430](https://github.com/sitespeedio/sitespeed.io/pull/4430).
15
+ * Log missing green domain info as info instead of error [#4433](https://github.com/sitespeedio/sitespeed.io/pull/4433).
16
+
3
17
  ## 36.2.5 - 2025-02-03
4
18
  ### Fixed
5
19
  * The check for sending Android test through APIs was broken in 36.2.4, fixed in [#4428](https://github.com/sitespeedio/sitespeed.io/pull/4428).
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM sitespeedio/webbrowsers:chrome-132.0-firefox-134.0-edge-132.0
1
+ FROM sitespeedio/webbrowsers:chrome-133.0-firefox-135.0-edge-132.0
2
2
 
3
3
  ARG TARGETPLATFORM=linux/amd64
4
4
 
package/docs/README.md ADDED
@@ -0,0 +1,10 @@
1
+ Documentation for sitespeed.io
2
+ ================
3
+
4
+ First make sure you have Bundler: <code>gem install bundler</code>
5
+
6
+ If you run on a Mac OS make sure you have xcode-select installed: <code>xcode-select --install</code>
7
+
8
+ To run it locally: <code>bundle install && bundle exec jekyll serve --baseurl ''</code>
9
+
10
+ Checkout http://localhost:4000/
@@ -23,7 +23,8 @@ const DEFAULT_METRICS_PAGESUMMARY = [
23
23
  'loadingExperience.*.INTERACTION_TO_NEXT_PAINT_MS.*',
24
24
  'loadingExperience.*.ROUND_TRIP_TIME_MS.*',
25
25
  'loadingExperience.*.NAVIGATION_TYPES_FRACTIONS.*',
26
- 'loadingExperience.*.FORM_FACTORS_FRACTIONS.*'
26
+ 'loadingExperience.*.FORM_FACTORS_FRACTIONS.*',
27
+ 'loadingExperience.*.LCP_RESOURCE_TYPES_FRACTIONS.*'
27
28
  ];
28
29
  const DEFAULT_METRICS_SUMMARY = [
29
30
  'originLoadingExperience.*.FIRST_CONTENTFUL_PAINT_MS.*',
@@ -33,7 +34,8 @@ const DEFAULT_METRICS_SUMMARY = [
33
34
  'originLoadingExperience.*.INTERACTION_TO_NEXT_PAINT_MS.*',
34
35
  'originLoadingExperience.*.ROUND_TRIP_TIME_MS.*',
35
36
  'originLoadingExperience.*.NAVIGATION_TYPES_FRACTIONS.*',
36
- 'originLoadingExperience.*.FORM_FACTORS_FRACTIONS.*'
37
+ 'originLoadingExperience.*.FORM_FACTORS_FRACTIONS.*',
38
+ 'originLoadingExperience.*.LCP_RESOURCE_TYPES_FRACTIONS.*'
37
39
  ];
38
40
 
39
41
  function wait(ms) {
@@ -11,7 +11,7 @@ mixin sizeCell(title, size)
11
11
  td.number(data-title=title, data-value= size)= h.size.format(size)
12
12
 
13
13
  - const crux = pageInfo.data.crux.pageSummary;
14
- - const metrics = {round_trip_time: 'Round trip time', experimental_time_to_first_byte: 'Time to first byte (TTFB)', first_contentful_paint:'First Contentful Paint (FCP)', largest_contentful_paint: 'Largest Contentful Paint (LCP)', cumulative_layout_shift: 'Cumulative Layout Shift (CLS)', interaction_to_next_paint: 'Interaction to next paint (INP)'};
14
+ - const metrics = {round_trip_time: 'Round Trip Time (RTT)', experimental_time_to_first_byte: 'Time To First Byte (TTFB)', first_contentful_paint:'First Contentful Paint (FCP)', largest_contentful_paint: 'Largest Contentful Paint (LCP)', cumulative_layout_shift: 'Cumulative Layout Shift (CLS)', interaction_to_next_paint: 'Interaction to Next Paint (INP)'};
15
15
  - const experiences = ['loadingExperience','originLoadingExperience'];
16
16
 
17
17
  if experiences
@@ -106,6 +106,46 @@ if experiences
106
106
  td Prerender
107
107
  td #{Number(crux[experience][formFactor].NAVIGATION_TYPES_FRACTIONS.prerender *100).toFixed(2)}%
108
108
 
109
+ if crux[experience][formFactor].LCP_RESOURCE_TYPES_FRACTIONS
110
+ h4 Largest Contentful Paint resource type
111
+ table
112
+ thead
113
+ tr
114
+ th Element type
115
+ th Value
116
+ tbody
117
+ tr
118
+ td Text
119
+ td #{Number(crux[experience][formFactor].LCP_RESOURCE_TYPES_FRACTIONS.text *100).toFixed(2)}%
120
+ tr
121
+ td Image
122
+ td #{Number(crux[experience][formFactor].LCP_RESOURCE_TYPES_FRACTIONS.image *100).toFixed(2)}%
123
+ h4 Largest Contentful Paint image extra information
124
+ table
125
+ thead
126
+ tr
127
+ th Metric
128
+ th 75 p
129
+ tbody
130
+ tr
131
+ td Largest Contentful Paint
132
+ td #{h.time.ms(crux[experience][formFactor].LARGEST_CONTENTFUL_PAINT_MS.p75)}
133
+ if crux[experience][formFactor].LCP_IMAGE_RESOURCE_LOAD_DURATION_MS
134
+ tr
135
+ td Image load duration
136
+ td #{h.time.ms(crux[experience][formFactor].LCP_IMAGE_RESOURCE_LOAD_DURATION_MS.p75)}
137
+ if crux[experience][formFactor].LCP_IMAGE_RESOURCE_LOAD_DELAY_MS
138
+ tr
139
+ td Image load delay
140
+ td #{h.time.ms(crux[experience][formFactor].LCP_IMAGE_RESOURCE_LOAD_DELAY_MS.p75)}
141
+ if crux[experience][formFactor].LCP_IMAGE_ELEMENT_RENDER_DELAY_MS
142
+ tr
143
+ td Image render delay
144
+ td #{h.time.ms(crux[experience][formFactor].LCP_IMAGE_ELEMENT_RENDER_DELAY_MS.p75)}
145
+ if crux[experience][formFactor].LCP_IMAGE_TTFB_MS
146
+ tr
147
+ td Image TTFB
148
+ td #{h.time.ms(crux[experience][formFactor].LCP_IMAGE_TTFB_MS.p75)}
109
149
  h4 Distribution
110
150
  - let cruxus = `${experience}.${formFactor}.data.record.metrics`;
111
151
  - let FCPs = [Number(get(crux, `${cruxus}.first_contentful_paint.histogram[0].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.first_contentful_paint.histogram[1].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.first_contentful_paint.histogram[2].density`, 0)*100).toFixed(2)];
@@ -118,6 +158,8 @@ if experiences
118
158
 
119
159
  - let ITNPs = [Number(get(crux, `${cruxus}.interaction_to_next_paint.histogram[0].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.interaction_to_next_paint.histogram[1].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.interaction_to_next_paint.histogram[2].density`, 0)*100).toFixed(2)];
120
160
 
161
+ - let RTTs = [Number(get(crux, `${cruxus}.round_trip_time.histogram[0].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.round_trip_time.histogram[1].density`, 0) * 100).toFixed(2), Number(get(crux, `${cruxus}.round_trip_time.histogram[2].density`, 0)*100).toFixed(2)];
162
+
121
163
  script(type='text/javascript').
122
164
  document.addEventListener("DOMContentLoaded", function() {
123
165
 
@@ -143,33 +185,35 @@ if experiences
143
185
  drawPie('#chartCLS#{experience + formFactor}', [#{CLSs}], ['Good: #{CLSs[0]}%', 'Need improvement: #{CLSs[1]}%', 'Poor: #{CLSs[2]}%']);
144
186
  drawPie('#chartTTFB#{experience + formFactor}', [#{TTFBs}], ['Good: #{TTFBs[0]}%', 'Need improvement: #{TTFBs[1]}%', 'Poor: #{TTFBs[2]}%']);
145
187
  drawPie('#chartITNP#{experience + formFactor}', [#{ITNPs}], ['Good: #{ITNPs[0]}%', 'Need improvement: #{ITNPs[1]}%', 'Poor: #{ITNPs[2]}%']);
146
-
188
+ drawPie('#chartRTT#{experience + formFactor}', [#{RTTs}], ['Low: #{RTTs[0]}%', 'Medium: #{RTTs[1]}%', 'High: #{RTTs[2]}%']);
147
189
 
148
190
  });
149
191
  .responsive
150
192
  table
151
193
  tr
194
+ th #{metrics['round_trip_time']}
152
195
  th #{metrics['experimental_time_to_first_byte']}
153
- th #{metrics['first_contentful_paint']}
154
196
  tr
197
+ td(data-title=metrics['round_trip_time'])
198
+ .ct-chart(id='chartRTT' + experience + formFactor)
155
199
  td(data-title=metrics['experimental_time_to_first_byte'])
156
200
  .ct-chart(id='chartTTFB' + experience + formFactor)
157
- td(data-title=metrics['first_contentful_paint'])
158
- .ct-chart(id='chartFCP' + experience + formFactor)
159
201
  tr
202
+ th #{metrics['first_contentful_paint']}
160
203
  th #{metrics['largest_contentful_paint']}
161
- th #{metrics['cumulative_layout_shift']}
162
204
  tr
205
+ td(data-title=metrics['first_contentful_paint'])
206
+ .ct-chart(id='chartFCP' + experience + formFactor)
163
207
  td(data-title=metrics['largest_contentful_paint'])
164
208
  .ct-chart(id='chartLCP' + experience + formFactor)
165
- td(data-title=metrics['cumulative_layout_shift'])
166
- .ct-chart(id='chartCLS' + experience + formFactor)
167
209
  tr
168
210
  th #{metrics['interaction_to_next_paint']}
169
- th
211
+ th #{metrics['cumulative_layout_shift']}
170
212
  tr
171
213
  td(data-title=metrics['interaction_to_next_paint'])
172
- .ct-chart(id='chartITNP' + experience + formFactor)
173
- td
214
+ .ct-chart(id='chartITNP' + experience + formFactor)
215
+ td(data-title=metrics['cumulative_layout_shift'])
216
+ .ct-chart(id='chartCLS' + experience + formFactor)
217
+
174
218
  else
175
219
  p No data availible in the Chrome User Experience report.
@@ -65,7 +65,48 @@ export function repackage(cruxResult) {
65
65
 
66
66
  if (cruxResult.record.metrics.round_trip_time) {
67
67
  result.ROUND_TRIP_TIME_MS = {
68
- p75: cruxResult.record.metrics.round_trip_time.percentiles.p75
68
+ p75: cruxResult.record.metrics.round_trip_time.percentiles.p75,
69
+ low: cruxResult.record.metrics.round_trip_time.histogram[0].density,
70
+ medium: cruxResult.record.metrics.round_trip_time.histogram[1].density,
71
+ high: cruxResult.record.metrics.round_trip_time.histogram[2].density
72
+ };
73
+ }
74
+
75
+ if (
76
+ cruxResult.record.metrics
77
+ .largest_contentful_paint_image_resource_load_duration
78
+ ) {
79
+ result.LCP_IMAGE_RESOURCE_LOAD_DURATION_MS = {
80
+ p75: cruxResult.record.metrics
81
+ .largest_contentful_paint_image_resource_load_duration.percentiles.p75
82
+ };
83
+ }
84
+
85
+ if (
86
+ cruxResult.record.metrics.largest_contentful_paint_image_resource_load_delay
87
+ ) {
88
+ result.LCP_IMAGE_RESOURCE_LOAD_DELAY_MS = {
89
+ p75: cruxResult.record.metrics
90
+ .largest_contentful_paint_image_resource_load_delay.percentiles.p75
91
+ };
92
+ }
93
+
94
+ if (
95
+ cruxResult.record.metrics
96
+ .largest_contentful_paint_image_element_render_delay
97
+ ) {
98
+ result.LCP_IMAGE_ELEMENT_RENDER_DELAY_MS = {
99
+ p75: cruxResult.record.metrics
100
+ .largest_contentful_paint_image_element_render_delay.percentiles.p75
101
+ };
102
+ }
103
+
104
+ if (
105
+ cruxResult.record.metrics.largest_contentful_paint_image_time_to_first_byte
106
+ ) {
107
+ result.LCP_IMAGE_TTFB_MS = {
108
+ p75: cruxResult.record.metrics
109
+ .largest_contentful_paint_image_time_to_first_byte.percentiles.p75
69
110
  };
70
111
  }
71
112
 
@@ -80,6 +121,19 @@ export function repackage(cruxResult) {
80
121
  };
81
122
  }
82
123
 
124
+ if (
125
+ cruxResult.record.metrics.largest_contentful_paint_resource_type &&
126
+ cruxResult.record.metrics.largest_contentful_paint_resource_type.fractions
127
+ ) {
128
+ result.LCP_RESOURCE_TYPES_FRACTIONS = {
129
+ text: cruxResult.record.metrics.largest_contentful_paint_resource_type
130
+ .fractions.text,
131
+ image:
132
+ cruxResult.record.metrics.largest_contentful_paint_resource_type
133
+ .fractions.image
134
+ };
135
+ }
136
+
83
137
  if (
84
138
  cruxResult.record.metrics.navigation_types &&
85
139
  cruxResult.record.metrics.navigation_types.fractions
@@ -80,40 +80,45 @@ export class DomainsAggregator {
80
80
  if (this.groups[mainDomain] === undefined) {
81
81
  this.groups[mainDomain] = {};
82
82
  }
83
- const firstPageId = har.log.pages[0].id;
84
83
 
85
- for (const entry of har.log.entries) {
86
- if (entry.pageref !== firstPageId) {
87
- // Only pick the first request out of multiple runs.
88
- continue;
89
- }
90
-
91
- const domainName = parseDomainName(entry.request.url),
92
- domain = this.domains[domainName] || getDomain(domainName),
93
- groupDomain =
94
- this.groups[mainDomain][domainName] || getDomain(domainName),
95
- totalTime = entry.time;
84
+ if (har.log.pages.length > 0) {
85
+ const firstPageId = har.log.pages[0].id;
96
86
 
97
- domain.requestCount++;
98
- groupDomain.requestCount++;
87
+ for (const entry of har.log.entries) {
88
+ if (entry.pageref !== firstPageId) {
89
+ // Only pick the first request out of multiple runs.
90
+ continue;
91
+ }
99
92
 
100
- if (isValidTiming(totalTime)) {
101
- domain.totalTime.push(totalTime);
102
- groupDomain.totalTime.push(totalTime);
103
- } else {
104
- log.debug('Missing time from har entry for url: ' + entry.request.url);
105
- }
93
+ const domainName = parseDomainName(entry.request.url),
94
+ domain = this.domains[domainName] || getDomain(domainName),
95
+ groupDomain =
96
+ this.groups[mainDomain][domainName] || getDomain(domainName),
97
+ totalTime = entry.time;
98
+
99
+ domain.requestCount++;
100
+ groupDomain.requestCount++;
101
+
102
+ if (isValidTiming(totalTime)) {
103
+ domain.totalTime.push(totalTime);
104
+ groupDomain.totalTime.push(totalTime);
105
+ } else {
106
+ log.debug(
107
+ 'Missing time from har entry for url: ' + entry.request.url
108
+ );
109
+ }
106
110
 
107
- for (const name of timingNames) {
108
- const timing = entry.timings[name];
111
+ for (const name of timingNames) {
112
+ const timing = entry.timings[name];
109
113
 
110
- if (isValidTiming(timing)) {
111
- domain[name].push(timing);
112
- groupDomain[name].push(timing);
114
+ if (isValidTiming(timing)) {
115
+ domain[name].push(timing);
116
+ groupDomain[name].push(timing);
117
+ }
113
118
  }
119
+ this.domains[domainName] = domain;
120
+ this.groups[mainDomain][domainName] = groupDomain;
114
121
  }
115
- this.domains[domainName] = domain;
116
- this.groups[mainDomain][domainName] = groupDomain;
117
122
  }
118
123
  }
119
124
  summarize() {
@@ -94,8 +94,8 @@ export default class SustainablePlugin extends SitespeedioPlugin {
94
94
  try {
95
95
  this.data = await loadJSON(greenDomainJSONpath);
96
96
  } catch {
97
- log.error('Green domain local file is missing on disk.');
98
- log.error(
97
+ log.info('Green domain local file is missing on disk.');
98
+ log.info(
99
99
  'Run `DOWNLOAD_URL2GREEN=true npm install sitespeed.io` when you install to install the file locally or use the online API using `--sustainable.useGreenWebHostingAPI true` to check if a domain is green hosted.'
100
100
  );
101
101
  this.data = {};
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "sitespeed.io",
3
- "version": "36.2.5",
3
+ "version": "36.4.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "sitespeed.io",
9
- "version": "36.2.5",
9
+ "version": "36.4.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -18,7 +18,7 @@
18
18
  "@slack/webhook": "7.0.4",
19
19
  "@tgwf/co2": "0.16.4",
20
20
  "axe-core": "4.10.2",
21
- "browsertime": "24.1.1",
21
+ "browsertime": "24.2.0",
22
22
  "coach-core": "8.1.1",
23
23
  "dayjs": "1.11.11",
24
24
  "fast-crc32c": "2.0.0",
@@ -1821,9 +1821,9 @@
1821
1821
  }
1822
1822
  },
1823
1823
  "node_modules/@sitespeed.io/chromedriver": {
1824
- "version": "132.0.6834-83",
1825
- "resolved": "https://registry.npmjs.org/@sitespeed.io/chromedriver/-/chromedriver-132.0.6834-83.tgz",
1826
- "integrity": "sha512-JHyO6MHW8t6fZwkpTpSqSk1w18YRJxg0Joym00GHlnB6uH15+//PBYeKuvnjdZTROlp/6FK3Pqo4hTE2Kql45g==",
1824
+ "version": "133.0.6943-53",
1825
+ "resolved": "https://registry.npmjs.org/@sitespeed.io/chromedriver/-/chromedriver-133.0.6943-53.tgz",
1826
+ "integrity": "sha512-AnzM2FoKEkdjCFAiC/zIJSjoHZmMxHL1utNyBAggXz+RsazC0v09RIGOS8cF2tmGEwJP9I47z56l98w4pGaS9w==",
1827
1827
  "hasInstallScript": true,
1828
1828
  "dependencies": {
1829
1829
  "node-downloader-helper": "2.1.9",
@@ -3329,12 +3329,12 @@
3329
3329
  }
3330
3330
  },
3331
3331
  "node_modules/browsertime": {
3332
- "version": "24.1.1",
3333
- "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-24.1.1.tgz",
3334
- "integrity": "sha512-VD5oeD5mxwqqlw18laZOUn0NKMP7k9kVE0ZMea2whNcyzAAclzaPFPhac5OGDE7ZRGwpLFIFryXPZSx9haXxwg==",
3332
+ "version": "24.2.0",
3333
+ "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-24.2.0.tgz",
3334
+ "integrity": "sha512-qC4sLm1tf18xdc5Hirpd6RRd4mz8T2dv+/7iImHG3U6BC8GoIio0picEBbtdB8dFXp0VgCBQwg1nFeSIf/HXBA==",
3335
3335
  "dependencies": {
3336
3336
  "@devicefarmer/adbkit": "3.3.8",
3337
- "@sitespeed.io/chromedriver": "132.0.6834-83",
3337
+ "@sitespeed.io/chromedriver": "133.0.6943-53",
3338
3338
  "@sitespeed.io/edgedriver": "132.0.2957-115",
3339
3339
  "@sitespeed.io/geckodriver": "0.35.0-1",
3340
3340
  "@sitespeed.io/log": "0.2.6",
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "sitespeed.io": "./bin/sitespeed.js",
6
6
  "sitespeed.io-wpr": "./bin/browsertimeWebPageReplay.js"
7
7
  },
8
- "version": "36.2.5",
8
+ "version": "36.4.0",
9
9
  "description": "sitespeed.io is an open-source tool for comprehensive web performance analysis, enabling you to test, monitor, and optimize your website’s speed using real browsers in various environments.",
10
10
  "keywords": [
11
11
  "performance",
@@ -89,7 +89,7 @@
89
89
  "@tgwf/co2": "0.16.4",
90
90
  "@slack/webhook": "7.0.4",
91
91
  "axe-core": "4.10.2",
92
- "browsertime": "24.1.1",
92
+ "browsertime": "24.2.0",
93
93
  "coach-core": "8.1.1",
94
94
  "dayjs": "1.11.11",
95
95
  "fast-crc32c": "2.0.0",