browsertime 26.3.2 → 27.0.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 (105) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +1 -1
  3. package/browserscripts/timings/softNavigations.js +94 -0
  4. package/lib/chrome/har.js +36 -5
  5. package/lib/chrome/settings/chromeAndroidOptions.js +2 -1
  6. package/lib/chrome/webdriver/chromium.js +39 -0
  7. package/lib/chrome/webdriver/setupChromiumOptions.js +86 -31
  8. package/lib/core/engine/collector.js +340 -317
  9. package/lib/core/engine/command/addText.js +93 -12
  10. package/lib/core/engine/command/click.js +239 -139
  11. package/lib/core/engine/command/commandHelper.js +45 -0
  12. package/lib/core/engine/command/cookie.js +85 -0
  13. package/lib/core/engine/command/element.js +37 -2
  14. package/lib/core/engine/command/javaScript.js +14 -24
  15. package/lib/core/engine/command/measure/screenshots.js +114 -0
  16. package/lib/core/engine/command/measure/video.js +85 -0
  17. package/lib/core/engine/command/measure.js +104 -180
  18. package/lib/core/engine/command/mouse/clickAndHold.js +6 -3
  19. package/lib/core/engine/command/mouse/contextClick.js +56 -4
  20. package/lib/core/engine/command/mouse/doubleClick.js +49 -1
  21. package/lib/core/engine/command/mouse/mouseMove.js +50 -1
  22. package/lib/core/engine/command/mouse/singleClick.js +165 -129
  23. package/lib/core/engine/command/navigation.js +6 -6
  24. package/lib/core/engine/command/select.js +95 -3
  25. package/lib/core/engine/command/selectorParser.js +58 -0
  26. package/lib/core/engine/command/set.js +55 -1
  27. package/lib/core/engine/command/wait.js +41 -2
  28. package/lib/core/engine/commands.js +550 -42
  29. package/lib/core/engine/index.js +206 -150
  30. package/lib/core/engine/iteration.js +269 -137
  31. package/lib/firefox/networkManager.js +22 -30
  32. package/lib/firefox/webdriver/builder.js +14 -0
  33. package/lib/safari/har.js +131 -0
  34. package/lib/safari/native/ios-capture +0 -0
  35. package/lib/safari/native/ios-capture.m +340 -0
  36. package/lib/safari/safariInspectorClient.js +322 -0
  37. package/lib/safari/webdriver/safari.js +303 -13
  38. package/lib/safari/webkitToCdpAdapter.js +241 -0
  39. package/lib/support/cli.js +13 -0
  40. package/lib/support/engineUtils.js +16 -5
  41. package/lib/support/getViewPort.js +5 -0
  42. package/lib/support/har/index.js +41 -3
  43. package/lib/support/usbPower.js +2 -3
  44. package/lib/video/postprocessing/finetune/addTextToVideo.js +4 -0
  45. package/lib/video/postprocessing/finetune/getFont.js +3 -3
  46. package/lib/video/postprocessing/finetune/getTimingMetrics.js +7 -0
  47. package/lib/video/postprocessing/finetune/index.js +26 -9
  48. package/lib/video/screenRecording/ios/iosDeviceRecorder.js +109 -0
  49. package/lib/video/screenRecording/recorder.js +2 -2
  50. package/lib/video/screenRecording/setOrangeBackground.js +3 -0
  51. package/package.json +11 -11
  52. package/types/core/engine/command/addText.d.ts +35 -6
  53. package/types/core/engine/command/addText.d.ts.map +1 -1
  54. package/types/core/engine/command/click.d.ts +87 -16
  55. package/types/core/engine/command/click.d.ts.map +1 -1
  56. package/types/core/engine/command/commandHelper.d.ts +16 -0
  57. package/types/core/engine/command/commandHelper.d.ts.map +1 -0
  58. package/types/core/engine/command/cookie.d.ts +65 -0
  59. package/types/core/engine/command/cookie.d.ts.map +1 -0
  60. package/types/core/engine/command/element.d.ts +19 -1
  61. package/types/core/engine/command/element.d.ts.map +1 -1
  62. package/types/core/engine/command/javaScript.d.ts +4 -0
  63. package/types/core/engine/command/javaScript.d.ts.map +1 -1
  64. package/types/core/engine/command/measure/screenshots.d.ts +15 -0
  65. package/types/core/engine/command/measure/screenshots.d.ts.map +1 -0
  66. package/types/core/engine/command/measure/video.d.ts +26 -0
  67. package/types/core/engine/command/measure/video.d.ts.map +1 -0
  68. package/types/core/engine/command/measure.d.ts +59 -33
  69. package/types/core/engine/command/measure.d.ts.map +1 -1
  70. package/types/core/engine/command/mouse/clickAndHold.d.ts.map +1 -1
  71. package/types/core/engine/command/mouse/contextClick.d.ts +28 -4
  72. package/types/core/engine/command/mouse/contextClick.d.ts.map +1 -1
  73. package/types/core/engine/command/mouse/doubleClick.d.ts +24 -4
  74. package/types/core/engine/command/mouse/doubleClick.d.ts.map +1 -1
  75. package/types/core/engine/command/mouse/mouseMove.d.ts +30 -5
  76. package/types/core/engine/command/mouse/mouseMove.d.ts.map +1 -1
  77. package/types/core/engine/command/mouse/singleClick.d.ts +50 -13
  78. package/types/core/engine/command/mouse/singleClick.d.ts.map +1 -1
  79. package/types/core/engine/command/navigation.d.ts +3 -3
  80. package/types/core/engine/command/select.d.ts +45 -8
  81. package/types/core/engine/command/select.d.ts.map +1 -1
  82. package/types/core/engine/command/selectorParser.d.ts +20 -0
  83. package/types/core/engine/command/selectorParser.d.ts.map +1 -0
  84. package/types/core/engine/command/set.d.ts +34 -7
  85. package/types/core/engine/command/set.d.ts.map +1 -1
  86. package/types/core/engine/command/wait.d.ts +29 -6
  87. package/types/core/engine/command/wait.d.ts.map +1 -1
  88. package/types/core/engine/commands.d.ts +296 -28
  89. package/types/core/engine/commands.d.ts.map +1 -1
  90. package/types/support/engineUtils.d.ts.map +1 -1
  91. package/types/support/getViewPort.d.ts.map +1 -1
  92. package/types/support/usbPower.d.ts.map +1 -1
  93. package/types/video/postprocessing/finetune/addTextToVideo.d.ts.map +1 -1
  94. package/types/video/postprocessing/finetune/getTimingMetrics.d.ts.map +1 -1
  95. package/types/video/postprocessing/finetune/index.d.ts.map +1 -1
  96. package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts +28 -0
  97. package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts.map +1 -0
  98. package/types/video/screenRecording/recorder.d.ts +2 -2
  99. package/types/video/screenRecording/recorder.d.ts.map +1 -1
  100. package/types/video/screenRecording/setOrangeBackground.d.ts.map +1 -1
  101. package/types/video/video.d.ts +1 -1
  102. package/types/video/video.d.ts.map +1 -1
  103. package/visualmetrics/visualmetrics-portable.py +136 -136
  104. package/lib/video/screenRecording/ios/convertToMp4.js +0 -22
  105. package/lib/video/screenRecording/ios/iosRecorder.js +0 -68
@@ -142,37 +142,7 @@ export class Collector {
142
142
  */
143
143
  async perIteration(allData, index) {
144
144
  for (let data of allData) {
145
- const alias = data.alias;
146
- let url = data.url || this.urlFromCli;
147
-
148
- if (data.browserScripts && data.browserScripts.pageinfo) {
149
- const actualURL = data.browserScripts.pageinfo.url;
150
- // If the URL that we wanted to test do not match the actual URL used
151
- // which can happen in scripting when using alias and URLs has session etc in it
152
- if (url !== actualURL) {
153
- this.urlAndActualUrl[actualURL] = url;
154
- }
155
- }
156
- if (alias && !this.aliasAndUrl[alias]) {
157
- this.aliasAndUrl[alias] = url;
158
- } else if (alias && this.aliasAndUrl[alias]) {
159
- url = this.aliasAndUrl[alias];
160
- }
161
-
162
- const results = this.allResults[url] ?? getNewResult(url, this.options);
163
- results.info.description = allData.description;
164
- results.info.title = allData.title;
165
- results.info.browser.userAgent = getProperty(
166
- allData[0],
167
- 'browserScripts.browser.userAgent',
168
- ''
169
- );
170
-
171
- if (allData.screenshots && allData.screenshots.length > 0) {
172
- results.files.screenshot.push(allData.screenshots);
173
- }
174
-
175
- const statistics = this.allStats[url] ?? new Statistics();
145
+ const { url, results, statistics } = this._resolveURL(data, allData);
176
146
 
177
147
  // The user can add extra data in a script or post script
178
148
  if (data.extras) {
@@ -200,35 +170,8 @@ export class Collector {
200
170
  results.errors.push(data.error || []);
201
171
  // From each iteration, collect the result we want
202
172
  results.timestamps.push(data.timestamp);
203
- if (data.browserScripts) {
204
- results.browserScripts.push(data.browserScripts);
205
- // Add all browserscripts to the stats
206
- const equals = (a1, a2) => JSON.stringify(a1) === JSON.stringify(a2);
207
- statistics.addDeep(data.browserScripts, (keyPath, value) => {
208
- if (equals(keyPath.slice(-2), ['userTimings', 'marks'])) {
209
- // eslint-disable-next-line unicorn/no-array-reduce
210
- return value.reduce((result, mark) => {
211
- result[mark.name] = mark.startTime;
212
- return result;
213
- }, {});
214
- } else if (equals(keyPath.slice(-2), ['userTimings', 'measures'])) {
215
- // eslint-disable-next-line unicorn/no-array-reduce
216
- return value.reduce((result, mark) => {
217
- result[mark.name] = mark.duration;
218
- return result;
219
- }, {});
220
- } else if (equals(keyPath.slice(-1), ['resourceTimings'])) {
221
- return {};
222
- } else if (equals(keyPath.slice(-2), ['timings', 'serverTimings'])) {
223
- // eslint-disable-next-line unicorn/no-array-reduce
224
- return value.reduce((result, timing) => {
225
- result[timing.name] = timing.duration;
226
- return result;
227
- }, {});
228
- }
229
- return value;
230
- });
231
- }
173
+
174
+ this._collectBrowserScripts(data, statistics, results);
232
175
 
233
176
  if (data.alias) {
234
177
  results.info.alias = data.alias;
@@ -242,116 +185,11 @@ export class Collector {
242
185
  });
243
186
  }
244
187
 
245
- if (this.options.iterations > 1) {
246
- const ttfb = getProperty(
247
- data,
248
- 'browserScripts.timings.pageTimings.backEndTime'
249
- );
250
- const domContentLoaded = getProperty(
251
- data,
252
- 'browserScripts.timings.pageTimings.domContentLoadedTime'
253
- );
254
- const firstPaint = getProperty(
255
- data,
256
- 'browserScripts.timings.firstPaint'
257
- );
258
- const fcp = getProperty(
259
- data,
260
- "browserScripts.timings.paintTiming['first-contentful-paint']"
261
- );
262
- const pageLoadTime = getProperty(
263
- data,
264
- 'browserScripts.timings.pageTimings.pageLoadTime'
265
- );
266
- const lcp = getProperty(
267
- data,
268
- 'browserScripts.timings.largestContentfulPaint.renderTime'
269
- );
270
- const cls = getProperty(
271
- data,
272
- 'browserScripts.pageinfo.cumulativeLayoutShift'
273
- );
274
- const tbt = getProperty(data, 'cpu.longTasks.totalBlockingTime');
275
- const mem = getProperty(data, 'memory');
276
-
277
- log.info(
278
- `${url} ${ttfb ? formatMetric('TTFB', ttfb, false, true) + ' ' : ''}${
279
- domContentLoaded
280
- ? formatMetric(
281
- 'DOMContentLoaded',
282
- domContentLoaded,
283
- false,
284
- true
285
- ) + ' '
286
- : ''
287
- }${
288
- firstPaint
289
- ? formatMetric('firstPaint', firstPaint, false, true) + ' '
290
- : ''
291
- }${fcp ? formatMetric('FCP', fcp, false, true) + ' ' : ''}${
292
- lcp ? formatMetric('LCP', lcp, false, true) + ' ' : ''
293
- }${
294
- pageLoadTime
295
- ? formatMetric('Load', pageLoadTime, false, true) + ' '
296
- : ''
297
- }${tbt ? formatMetric('TBT', tbt, false, true) + ' ' : ''}${
298
- cls ? 'CLS:' + cls.toFixed(4) : ''
299
- }${
300
- mem
301
- ? formatMetric(
302
- 'Memory',
303
- Math.round(mem / 1024 / 1024),
304
- false,
305
- false
306
- ) + 'mb '
307
- : ''
308
- }`
309
- );
310
- }
188
+ this._logIterationMetrics(data, url);
311
189
 
312
190
  if (data.visualMetrics) {
313
191
  if (this.options.iterations > 1) {
314
- log.info(
315
- `VisualMetrics: ${formatMetric(
316
- 'FirstVisualChange',
317
- data.visualMetrics.FirstVisualChange,
318
- false,
319
- true
320
- )} ${formatMetric(
321
- 'SpeedIndex',
322
- data.visualMetrics.SpeedIndex,
323
- false,
324
- true
325
- )}${
326
- data.visualMetrics.PerceptualSpeedIndex
327
- ? formatMetric(
328
- ' PerceptualSpeedIndex',
329
- data.visualMetrics.PerceptualSpeedIndex,
330
- false,
331
- true
332
- )
333
- : ''
334
- }${
335
- data.visualMetrics.ContentfulSpeedIndex
336
- ? formatMetric(
337
- ' ContentfulSpeedIndex',
338
- data.visualMetrics.ContentfulSpeedIndex,
339
- false,
340
- true
341
- )
342
- : ''
343
- } ${formatMetric(
344
- 'VisualComplete85',
345
- data.visualMetrics.VisualComplete85,
346
- false,
347
- true
348
- )} ${formatMetric(
349
- 'LastVisualChange',
350
- data.visualMetrics.LastVisualChange,
351
- false,
352
- true
353
- )}`
354
- );
192
+ this._logVisualMetrics(data);
355
193
  }
356
194
  for (let key of Object.keys(data.visualMetrics)) {
357
195
  // Skip VisualProgress/ContentfulProgress etc
@@ -363,195 +201,380 @@ export class Collector {
363
201
  }
364
202
  results.visualMetrics.push(data.visualMetrics);
365
203
  }
204
+
205
+ this._collectPerformanceData(data, statistics, results);
206
+ this._computeDeltaToTTFB(data, results, statistics);
207
+
208
+ // Store all extra JSON metrics
366
209
  // Put all work that involves storing metrics to disk in a
367
210
  // array and the promise them all later on
368
211
  const extraWork = [];
369
-
370
- if (
371
- data.browserScripts &&
372
- data.browserScripts.pageinfo &&
373
- data.browserScripts.pageinfo.longTask
374
- ) {
375
- for (let longTask of data.browserScripts.pageinfo.longTask) {
376
- statistics.addDeep({
377
- cpu: {
378
- longTasks: {
379
- durations: longTask.duration
380
- }
381
- }
382
- });
383
- }
384
- }
385
-
386
- // Add CPU data (if we have it)
387
- if (data.cpu) {
388
- results.cpu.push(data.cpu);
389
- // We skip adding stats per URL .., we should do it per domain instead in sitespeed.io
390
- if (data.cpu.categories) {
391
- statistics.addDeep({
392
- cpu: {
393
- categories: data.cpu.categories,
394
- events: data.cpu.events
395
- }
396
- });
397
- }
398
- if (data.cpu.longTasks) {
399
- statistics.addDeep({
400
- cpu: {
401
- longTasks: data.cpu.longTasks
402
- }
403
- });
404
- }
212
+ for (let filename of Object.keys(data.extraJson)) {
213
+ extraWork.push(
214
+ this.storageManager.writeJson(
215
+ path.join(pathToFolder(url, this.options), filename),
216
+ data.extraJson[filename],
217
+ true
218
+ )
219
+ );
405
220
  }
406
221
 
407
- if (data.googleWebVitals) {
408
- results.googleWebVitals.push(data.googleWebVitals);
409
- statistics.addDeep({
410
- googleWebVitals: data.googleWebVitals
411
- });
412
- }
222
+ this._collectFilePaths(url, index, results);
413
223
 
414
- if (data.renderBlocking) {
415
- results.renderBlocking.push(data.renderBlocking);
224
+ await Promise.all(extraWork);
225
+ this.allStats[url] = statistics;
226
+ this.allResults[url] = results;
227
+ }
228
+ }
416
229
 
417
- statistics.addDeep({
418
- renderBlocking: {
419
- recalculateStyle: data.renderBlocking.recalculateStyle
420
- }
421
- });
422
- }
230
+ _resolveURL(data, allData) {
231
+ const alias = data.alias;
232
+ let url = data.url || this.urlFromCli;
423
233
 
424
- // Add power data (if we have it)
425
- if (data.power) {
426
- results.android.power.push(data.power);
427
- statistics.addDeep({
428
- android: { power: data.power }
429
- });
234
+ if (data.browserScripts && data.browserScripts.pageinfo) {
235
+ const actualURL = data.browserScripts.pageinfo.url;
236
+ // If the URL that we wanted to test do not match the actual URL used
237
+ // which can happen in scripting when using alias and URLs has session etc in it
238
+ if (url !== actualURL) {
239
+ this.urlAndActualUrl[actualURL] = url;
430
240
  }
241
+ }
242
+ if (alias && !this.aliasAndUrl[alias]) {
243
+ this.aliasAndUrl[alias] = url;
244
+ } else if (alias && this.aliasAndUrl[alias]) {
245
+ url = this.aliasAndUrl[alias];
246
+ }
431
247
 
432
- // Add Firefox perfStats if available
433
- if (this.options.firefox && this.options.firefox.perfStats) {
434
- if (!results.geckoPerfStats) {
435
- results.geckoPerfStats = [];
436
- }
437
- results.geckoPerfStats.push(data.perfStats);
438
- }
248
+ const results = this.allResults[url] ?? getNewResult(url, this.options);
249
+ results.info.description = allData.description;
250
+ results.info.title = allData.title;
251
+ results.info.browser.userAgent = getProperty(
252
+ allData[0],
253
+ 'browserScripts.browser.userAgent',
254
+ ''
255
+ );
256
+
257
+ if (allData.screenshots && allData.screenshots.length > 0) {
258
+ results.files.screenshot.push(allData.screenshots);
259
+ }
439
260
 
440
- // Add Firefox cpu power
441
- if (data.powerConsumption) {
442
- if (!results.powerConsumption) {
443
- results.powerConsumption = [];
444
- }
445
- results.powerConsumption.push(data.powerConsumption);
446
- statistics.addDeep({
447
- powerConsumption: data.powerConsumption
448
- });
449
- }
261
+ const statistics = this.allStats[url] ?? new Statistics();
450
262
 
451
- // Add total memory
452
- if (this.options.firefox && this.options.firefox.memoryReport) {
453
- statistics.addDeep({
454
- memory: data.memory
455
- });
263
+ return { url, results, statistics };
264
+ }
456
265
 
457
- if (!results.memory) {
458
- results.memory = [];
266
+ _collectBrowserScripts(data, statistics, results) {
267
+ if (data.browserScripts) {
268
+ results.browserScripts.push(data.browserScripts);
269
+ // Add all browserscripts to the stats
270
+ const equals = (a1, a2) => JSON.stringify(a1) === JSON.stringify(a2);
271
+ statistics.addDeep(data.browserScripts, (keyPath, value) => {
272
+ if (equals(keyPath.slice(-2), ['userTimings', 'marks'])) {
273
+ // eslint-disable-next-line unicorn/no-array-reduce
274
+ return value.reduce((result, mark) => {
275
+ result[mark.name] = mark.startTime;
276
+ return result;
277
+ }, {});
278
+ } else if (equals(keyPath.slice(-2), ['userTimings', 'measures'])) {
279
+ // eslint-disable-next-line unicorn/no-array-reduce
280
+ return value.reduce((result, mark) => {
281
+ result[mark.name] = mark.duration;
282
+ return result;
283
+ }, {});
284
+ } else if (equals(keyPath.slice(-1), ['resourceTimings'])) {
285
+ return {};
286
+ } else if (equals(keyPath.slice(-2), ['timings', 'serverTimings'])) {
287
+ // eslint-disable-next-line unicorn/no-array-reduce
288
+ return value.reduce((result, timing) => {
289
+ result[timing.name] = timing.duration;
290
+ return result;
291
+ }, {});
459
292
  }
460
- results.memory.push(data.memory);
461
- }
293
+ return value;
294
+ });
295
+ }
296
+ }
462
297
 
463
- // Add delta to TTFB
464
- const deltaToTTFB = {};
298
+ _logIterationMetrics(data, url) {
299
+ if (this.options.iterations > 1) {
300
+ const ttfb = getProperty(
301
+ data,
302
+ 'browserScripts.timings.pageTimings.backEndTime'
303
+ );
304
+ const domContentLoaded = getProperty(
305
+ data,
306
+ 'browserScripts.timings.pageTimings.domContentLoadedTime'
307
+ );
308
+ const firstPaint = getProperty(data, 'browserScripts.timings.firstPaint');
465
309
  const fcp = getProperty(
466
310
  data,
467
- 'browserScripts.timings.paintTiming.first-contentful-paint'
311
+ "browserScripts.timings.paintTiming['first-contentful-paint']"
312
+ );
313
+ const pageLoadTime = getProperty(
314
+ data,
315
+ 'browserScripts.timings.pageTimings.pageLoadTime'
468
316
  );
469
- const ttfb = getProperty(data, 'browserScripts.timings.ttfb');
470
317
  const lcp = getProperty(
471
318
  data,
472
319
  'browserScripts.timings.largestContentfulPaint.renderTime'
473
320
  );
474
- const firstVisualChange = getProperty(
321
+ const cls = getProperty(
475
322
  data,
476
- 'visualMetrics.FirstVisualChange'
323
+ 'browserScripts.pageinfo.cumulativeLayoutShift'
477
324
  );
478
- const lastVisualChange = getProperty(
479
- data,
480
- 'visualMetrics.LastVisualChange'
325
+ const tbt = getProperty(data, 'cpu.longTasks.totalBlockingTime');
326
+ const mem = getProperty(data, 'memory');
327
+
328
+ log.info(
329
+ `${url} ${ttfb ? formatMetric('TTFB', ttfb, false, true) + ' ' : ''}${
330
+ domContentLoaded
331
+ ? formatMetric('DOMContentLoaded', domContentLoaded, false, true) +
332
+ ' '
333
+ : ''
334
+ }${
335
+ firstPaint
336
+ ? formatMetric('firstPaint', firstPaint, false, true) + ' '
337
+ : ''
338
+ }${fcp ? formatMetric('FCP', fcp, false, true) + ' ' : ''}${
339
+ lcp ? formatMetric('LCP', lcp, false, true) + ' ' : ''
340
+ }${
341
+ pageLoadTime
342
+ ? formatMetric('Load', pageLoadTime, false, true) + ' '
343
+ : ''
344
+ }${tbt ? formatMetric('TBT', tbt, false, true) + ' ' : ''}${
345
+ cls ? 'CLS:' + cls.toFixed(4) : ''
346
+ }${
347
+ mem
348
+ ? formatMetric(
349
+ 'Memory',
350
+ Math.round(mem / 1024 / 1024),
351
+ false,
352
+ false
353
+ ) + 'mb '
354
+ : ''
355
+ }`
481
356
  );
357
+ }
358
+ }
482
359
 
483
- if (fcp) {
484
- deltaToTTFB['firstContentfulPaint'] = fcp - ttfb;
485
- }
486
- if (lcp) {
487
- deltaToTTFB['largestContentfulPaint'] = lcp - ttfb;
488
- }
489
- if (firstVisualChange) {
490
- deltaToTTFB['firstVisualChange'] = firstVisualChange - ttfb;
491
- }
492
- if (lastVisualChange) {
493
- deltaToTTFB['lastVisualChange'] = lastVisualChange - ttfb;
494
- }
495
- results.deltaToTTFB.push(deltaToTTFB);
496
- statistics.addDeep({ deltaToTFFB: deltaToTTFB });
360
+ _logVisualMetrics(data) {
361
+ log.info(
362
+ `VisualMetrics: ${formatMetric(
363
+ 'FirstVisualChange',
364
+ data.visualMetrics.FirstVisualChange,
365
+ false,
366
+ true
367
+ )} ${formatMetric(
368
+ 'SpeedIndex',
369
+ data.visualMetrics.SpeedIndex,
370
+ false,
371
+ true
372
+ )}${
373
+ data.visualMetrics.PerceptualSpeedIndex
374
+ ? formatMetric(
375
+ ' PerceptualSpeedIndex',
376
+ data.visualMetrics.PerceptualSpeedIndex,
377
+ false,
378
+ true
379
+ )
380
+ : ''
381
+ }${
382
+ data.visualMetrics.ContentfulSpeedIndex
383
+ ? formatMetric(
384
+ ' ContentfulSpeedIndex',
385
+ data.visualMetrics.ContentfulSpeedIndex,
386
+ false,
387
+ true
388
+ )
389
+ : ''
390
+ } ${formatMetric(
391
+ 'VisualComplete85',
392
+ data.visualMetrics.VisualComplete85,
393
+ false,
394
+ true
395
+ )} ${formatMetric(
396
+ 'LastVisualChange',
397
+ data.visualMetrics.LastVisualChange,
398
+ false,
399
+ true
400
+ )}`
401
+ );
402
+ }
497
403
 
498
- // Store all extra JSON metrics
499
- for (let filename of Object.keys(data.extraJson)) {
500
- extraWork.push(
501
- this.storageManager.writeJson(
502
- path.join(pathToFolder(url, this.options), filename),
503
- data.extraJson[filename],
504
- true
505
- )
506
- );
404
+ _collectPerformanceData(data, statistics, results) {
405
+ if (
406
+ data.browserScripts &&
407
+ data.browserScripts.pageinfo &&
408
+ data.browserScripts.pageinfo.longTask
409
+ ) {
410
+ for (let longTask of data.browserScripts.pageinfo.longTask) {
411
+ statistics.addDeep({
412
+ cpu: {
413
+ longTasks: {
414
+ durations: longTask.duration
415
+ }
416
+ }
417
+ });
507
418
  }
419
+ }
508
420
 
509
- if (this.options.video) {
510
- results.files.video.push(
511
- `${pathToFolder(url, this.options)}video/${index}.mp4`
512
- );
513
- }
514
- if (this.options.chrome && this.options.chrome.timeline) {
515
- results.files.timeline.push(
516
- `${pathToFolder(url, this.options)}trace-${index}.json.gz`
517
- );
421
+ // Add CPU data (if we have it)
422
+ if (data.cpu) {
423
+ results.cpu.push(data.cpu);
424
+ // We skip adding stats per URL .., we should do it per domain instead in sitespeed.io
425
+ if (data.cpu.categories) {
426
+ statistics.addDeep({
427
+ cpu: {
428
+ categories: data.cpu.categories,
429
+ events: data.cpu.events
430
+ }
431
+ });
518
432
  }
519
- if (this.options.chrome && this.options.chrome.collectConsoleLog) {
520
- results.files.consoleLog.push(
521
- `${pathToFolder(url, this.options)}console-${index}.json.gz`
522
- );
433
+ if (data.cpu.longTasks) {
434
+ statistics.addDeep({
435
+ cpu: {
436
+ longTasks: data.cpu.longTasks
437
+ }
438
+ });
523
439
  }
440
+ }
524
441
 
525
- if (this.options.chrome && this.options.chrome.collectNetLog) {
526
- results.files.netLog.push(
527
- `${pathToFolder(url, this.options)}chromeNetlog-${index}.json.gz`
528
- );
529
- }
442
+ if (data.googleWebVitals) {
443
+ results.googleWebVitals.push(data.googleWebVitals);
444
+ statistics.addDeep({
445
+ googleWebVitals: data.googleWebVitals
446
+ });
447
+ }
530
448
 
531
- if (this.options.chrome && this.options.chrome.collectPerfLog) {
532
- results.files.perfLog.push(
533
- `${pathToFolder(url, this.options)}chromePerflog-${index}.json.gz`
534
- );
449
+ if (data.renderBlocking) {
450
+ results.renderBlocking.push(data.renderBlocking);
451
+
452
+ statistics.addDeep({
453
+ renderBlocking: {
454
+ recalculateStyle: data.renderBlocking.recalculateStyle
455
+ }
456
+ });
457
+ }
458
+
459
+ // Add power data (if we have it)
460
+ if (data.power) {
461
+ results.android.power.push(data.power);
462
+ statistics.addDeep({
463
+ android: { power: data.power }
464
+ });
465
+ }
466
+
467
+ // Add Firefox perfStats if available
468
+ if (this.options.firefox && this.options.firefox.perfStats) {
469
+ if (!results.geckoPerfStats) {
470
+ results.geckoPerfStats = [];
535
471
  }
472
+ results.geckoPerfStats.push(data.perfStats);
473
+ }
536
474
 
537
- if (this.options.firefox && this.options.firefox.geckoProfiler) {
538
- const name = this.options.enableProfileRun
539
- ? `geckoProfile-${index}-extra.json.gz`
540
- : `geckoProfile-${index}.json.gz`;
541
- results.files.geckoProfiles.push(
542
- `${pathToFolder(url, this.options)}${name}`
543
- );
475
+ // Add Firefox cpu power
476
+ if (data.powerConsumption) {
477
+ if (!results.powerConsumption) {
478
+ results.powerConsumption = [];
544
479
  }
480
+ results.powerConsumption.push(data.powerConsumption);
481
+ statistics.addDeep({
482
+ powerConsumption: data.powerConsumption
483
+ });
484
+ }
545
485
 
546
- if (this.options.firefox && this.options.firefox.memoryReport) {
547
- results.files.memoryReports.push(
548
- `${pathToFolder(url, this.options)}memory-report-${index}.json.gz`
549
- );
486
+ // Add total memory
487
+ if (this.options.firefox && this.options.firefox.memoryReport) {
488
+ statistics.addDeep({
489
+ memory: data.memory
490
+ });
491
+
492
+ if (!results.memory) {
493
+ results.memory = [];
550
494
  }
495
+ results.memory.push(data.memory);
496
+ }
497
+ }
551
498
 
552
- await Promise.all(extraWork);
553
- this.allStats[url] = statistics;
554
- this.allResults[url] = results;
499
+ _computeDeltaToTTFB(data, results, statistics) {
500
+ // Add delta to TTFB
501
+ const deltaToTTFB = {};
502
+ const fcp = getProperty(
503
+ data,
504
+ 'browserScripts.timings.paintTiming.first-contentful-paint'
505
+ );
506
+ const ttfb = getProperty(data, 'browserScripts.timings.ttfb');
507
+ const lcp = getProperty(
508
+ data,
509
+ 'browserScripts.timings.largestContentfulPaint.renderTime'
510
+ );
511
+ const firstVisualChange = getProperty(
512
+ data,
513
+ 'visualMetrics.FirstVisualChange'
514
+ );
515
+ const lastVisualChange = getProperty(
516
+ data,
517
+ 'visualMetrics.LastVisualChange'
518
+ );
519
+
520
+ if (fcp) {
521
+ deltaToTTFB['firstContentfulPaint'] = fcp - ttfb;
522
+ }
523
+ if (lcp) {
524
+ deltaToTTFB['largestContentfulPaint'] = lcp - ttfb;
525
+ }
526
+ if (firstVisualChange) {
527
+ deltaToTTFB['firstVisualChange'] = firstVisualChange - ttfb;
528
+ }
529
+ if (lastVisualChange) {
530
+ deltaToTTFB['lastVisualChange'] = lastVisualChange - ttfb;
531
+ }
532
+ results.deltaToTTFB.push(deltaToTTFB);
533
+ statistics.addDeep({ deltaToTFFB: deltaToTTFB });
534
+ }
535
+
536
+ _collectFilePaths(url, index, results) {
537
+ if (this.options.video) {
538
+ results.files.video.push(
539
+ `${pathToFolder(url, this.options)}video/${index}.mp4`
540
+ );
541
+ }
542
+ if (this.options.chrome && this.options.chrome.timeline) {
543
+ results.files.timeline.push(
544
+ `${pathToFolder(url, this.options)}trace-${index}.json.gz`
545
+ );
546
+ }
547
+ if (this.options.chrome && this.options.chrome.collectConsoleLog) {
548
+ results.files.consoleLog.push(
549
+ `${pathToFolder(url, this.options)}console-${index}.json.gz`
550
+ );
551
+ }
552
+
553
+ if (this.options.chrome && this.options.chrome.collectNetLog) {
554
+ results.files.netLog.push(
555
+ `${pathToFolder(url, this.options)}chromeNetlog-${index}.json.gz`
556
+ );
557
+ }
558
+
559
+ if (this.options.chrome && this.options.chrome.collectPerfLog) {
560
+ results.files.perfLog.push(
561
+ `${pathToFolder(url, this.options)}chromePerflog-${index}.json.gz`
562
+ );
563
+ }
564
+
565
+ if (this.options.firefox && this.options.firefox.geckoProfiler) {
566
+ const name = this.options.enableProfileRun
567
+ ? `geckoProfile-${index}-extra.json.gz`
568
+ : `geckoProfile-${index}.json.gz`;
569
+ results.files.geckoProfiles.push(
570
+ `${pathToFolder(url, this.options)}${name}`
571
+ );
572
+ }
573
+
574
+ if (this.options.firefox && this.options.firefox.memoryReport) {
575
+ results.files.memoryReports.push(
576
+ `${pathToFolder(url, this.options)}memory-report-${index}.json.gz`
577
+ );
555
578
  }
556
579
  }
557
580
  }