browsertime 26.3.2 → 27.0.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +72 -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 +40 -1
  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 +268 -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 +277 -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
@@ -23,7 +23,6 @@ import { Collector } from './collector.js';
23
23
  import { Android, isAndroidConfigured } from '../../android/index.js';
24
24
  import { RootedDevice } from '../../android/root.js';
25
25
  import { run } from './run.js';
26
- import { IOSRecorder } from '../../video/screenRecording/ios/iosRecorder.js';
27
26
  import {
28
27
  loadPrePostScripts,
29
28
  loadScript,
@@ -76,154 +75,152 @@ export class Engine {
76
75
 
77
76
  options.safariDriverPort = await getAvailablePort([1234, 2000]);
78
77
 
79
- if (options.browser === 'safari' && options.safari.ios && options.video) {
80
- await IOSRecorder.activate();
81
- await delay(2000);
82
- }
83
-
84
78
  await addConnectivity(options);
85
79
 
86
80
  if (isAndroidConfigured(options)) {
87
- const android = new Android(options);
88
- await android._init();
89
- this.android = android;
81
+ await this._setupAndroid();
82
+ }
90
83
 
91
- if (options.android.gnirehtet === true && !options.webpagereplay) {
92
- this.gnirehtet = new Gnirehtet(options);
93
- await this.gnirehtet.start();
94
- }
84
+ if (options.safari && options.safari.useSimulator) {
85
+ await this._setupIOSSimulator();
86
+ }
95
87
 
96
- const phoneState = await android.getPhoneState();
97
- if (phoneState !== 'device') {
98
- const message = `The phone ${android.id} state is ${phoneState}`;
99
- log.error(message);
100
- throw new Error(message);
101
- }
88
+ return this.myXVFB.start();
89
+ }
102
90
 
103
- if (options.androidPretestPowerPress) {
104
- await android.clickPowerButton();
105
- }
91
+ async _setupAndroid() {
92
+ const options = this.options;
93
+ const android = new Android(options);
94
+ await android._init();
95
+ this.android = android;
106
96
 
107
- if (options.androidPretestPressHomeButton) {
108
- await android.pressHomeButton();
109
- }
97
+ if (options.android.gnirehtet === true && !options.webpagereplay) {
98
+ this.gnirehtet = new Gnirehtet(options);
99
+ await this.gnirehtet.start();
100
+ }
110
101
 
111
- if (options.androidVerifyNetwork) {
112
- const pingAddress = getProperty(
113
- options,
114
- 'androidPingAddress',
115
- '8.8.8.8'
116
- );
117
- const connection = await android.ping(pingAddress);
118
- if (!connection) {
119
- const message = `No internet connection for ${android.id}. Could not ping ${pingAddress}`;
120
- log.error(message);
121
- throw new Error(message);
122
- }
102
+ const phoneState = await android.getPhoneState();
103
+ if (phoneState !== 'device') {
104
+ const message = `The phone ${android.id} state is ${phoneState}`;
105
+ log.error(message);
106
+ throw new Error(message);
107
+ }
108
+
109
+ if (options.androidPretestPowerPress) {
110
+ await android.clickPowerButton();
111
+ }
112
+
113
+ if (options.androidPretestPressHomeButton) {
114
+ await android.pressHomeButton();
115
+ }
116
+
117
+ if (options.androidVerifyNetwork) {
118
+ const pingAddress = getProperty(options, 'androidPingAddress', '8.8.8.8');
119
+ const connection = await android.ping(pingAddress);
120
+ if (!connection) {
121
+ const message = `No internet connection for ${android.id}. Could not ping ${pingAddress}`;
122
+ log.error(message);
123
+ throw new Error(message);
123
124
  }
125
+ }
124
126
 
125
- await android.closeAppNotRespondingPopup();
127
+ await android.closeAppNotRespondingPopup();
126
128
 
127
- // Make sure screen is on!
128
- await android._runCommand('svc power stayon true');
129
+ // Make sure screen is on!
130
+ await android._runCommand('svc power stayon true');
129
131
 
130
- if (options.androidRooted) {
131
- const gotRoot = await android._runCommandAndGet(
132
- 'su - root -c "echo test"'
133
- );
134
- if (gotRoot.includes('not found')) {
135
- log.info('Your phone do not have su, is it really rooted?');
136
- } else {
137
- this.rooted = new RootedDevice(this.android, options);
138
- await this.rooted.start();
139
- }
132
+ if (options.androidRooted) {
133
+ const gotRoot = await android._runCommandAndGet(
134
+ 'su - root -c "echo test"'
135
+ );
136
+ if (gotRoot.includes('not found')) {
137
+ log.info('Your phone do not have su, is it really rooted?');
138
+ } else {
139
+ this.rooted = new RootedDevice(this.android, options);
140
+ await this.rooted.start();
140
141
  }
142
+ }
141
143
 
142
- if (options.androidBatteryTemperatureLimit) {
143
- const maxTries = options.androidBatteryTemperatureMaxTries || 20;
144
- let batteryTry = 0;
145
- let temporary = await android.getTemperature();
146
- const minTemporaryLimit = options.androidBatteryTemperatureLimit;
147
- const waitTime =
148
- (options.androidBatteryTemperatureWaitTimeInSeconds || 120) * 1000;
149
- if (temporary > minTemporaryLimit) {
150
- do {
151
- temporary = await android.getTemperature();
152
- log.info(
153
- 'Battery temperature is %s, waiting it to go down under %s, will sleep for %s s',
144
+ if (options.androidBatteryTemperatureLimit) {
145
+ const maxTries = options.androidBatteryTemperatureMaxTries || 20;
146
+ let batteryTry = 0;
147
+ let temporary = await android.getTemperature();
148
+ const minTemporaryLimit = options.androidBatteryTemperatureLimit;
149
+ const waitTime =
150
+ (options.androidBatteryTemperatureWaitTimeInSeconds || 120) * 1000;
151
+ if (temporary > minTemporaryLimit) {
152
+ do {
153
+ temporary = await android.getTemperature();
154
+ log.info(
155
+ 'Battery temperature is %s, waiting it to go down under %s, will sleep for %s s',
156
+ temporary,
157
+ minTemporaryLimit,
158
+ waitTime / 1000
159
+ );
160
+ batteryTry++;
161
+ if (batteryTry === maxTries) {
162
+ log.error(
163
+ 'Battery temperature (%s) never got under %s after %s tries.',
154
164
  temporary,
155
165
  minTemporaryLimit,
156
- waitTime / 1000
166
+ maxTries
157
167
  );
158
- batteryTry++;
159
- if (batteryTry === maxTries) {
160
- log.error(
161
- 'Battery temperature (%s) never got under %s after %s tries.',
162
- temporary,
163
- minTemporaryLimit,
164
- maxTries
165
- );
166
- if (options.androidBatteryTemperatureReboot) {
167
- log.info(
168
- 'Rebooting the device, will wait until ADB sees the device'
169
- );
170
- await android.reboot();
171
- }
172
- throw new Error(
173
- 'Battery temperature never got under the configured limit'
168
+ if (options.androidBatteryTemperatureReboot) {
169
+ log.info(
170
+ 'Rebooting the device, will wait until ADB sees the device'
174
171
  );
172
+ await android.reboot();
175
173
  }
176
- await delay(waitTime);
177
- } while (temporary > minTemporaryLimit);
178
- } else {
179
- log.info(
180
- 'Battery temperature is %s, lets start the tests',
181
- temporary
182
- );
183
- }
174
+ throw new Error(
175
+ 'Battery temperature never got under the configured limit'
176
+ );
177
+ }
178
+ await delay(waitTime);
179
+ } while (temporary > minTemporaryLimit);
180
+ } else {
181
+ log.info('Battery temperature is %s, lets start the tests', temporary);
184
182
  }
183
+ }
185
184
 
186
- if (options.connectivity && options.connectivity.engine === 'humble') {
187
- const wifiName = await android.getWifi();
188
- log.info('The phone is using the WiFi: %s', wifiName);
189
- }
185
+ if (options.connectivity && options.connectivity.engine === 'humble') {
186
+ const wifiName = await android.getWifi();
187
+ log.info('The phone is using the WiFi: %s', wifiName);
190
188
  }
189
+ }
191
190
 
192
- if (options.safari && options.safari.useSimulator) {
193
- // Start the simulator
194
- try {
195
- await command(
196
- 'open -a /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/',
197
- { shell: true }
198
- );
199
- await delay(4000);
200
- } catch (error) {
201
- log.error('Could not start the Simulator.app', error);
202
- throw error;
203
- }
191
+ async _setupIOSSimulator() {
192
+ const options = this.options;
193
+ // Start the simulator
194
+ try {
195
+ await command(
196
+ 'open -a /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/',
197
+ { shell: true }
198
+ );
199
+ await delay(4000);
200
+ } catch (error) {
201
+ log.error('Could not start the Simulator.app', error);
202
+ throw error;
203
+ }
204
204
 
205
- const { stdout } = await command('xcrun simctl list devices -j', {
206
- shell: true
207
- });
208
-
209
- const simulatedDevices = JSON.parse(stdout);
210
- const types = Object.keys(simulatedDevices.devices);
211
- for (let type of types) {
212
- for (let device of simulatedDevices.devices[type]) {
213
- if (device.udid === options.safari.deviceUDID) {
214
- this.iosSimulatorDeviceName = device.name;
215
- log.info(
216
- 'Running test on %s simulator using %s',
217
- this.iosSimulatorDeviceName,
218
- _arch()
219
- );
220
- break;
221
- }
205
+ const { stdout } = await command('xcrun simctl list devices -j', {
206
+ shell: true
207
+ });
208
+
209
+ const simulatedDevices = JSON.parse(stdout);
210
+ const types = Object.keys(simulatedDevices.devices);
211
+ for (let type of types) {
212
+ for (let device of simulatedDevices.devices[type]) {
213
+ if (device.udid === options.safari.deviceUDID) {
214
+ this.iosSimulatorDeviceName = device.name;
215
+ log.info(
216
+ 'Running test on %s simulator using %s',
217
+ this.iosSimulatorDeviceName,
218
+ _arch()
219
+ );
220
+ break;
222
221
  }
223
222
  }
224
223
  }
225
-
226
- return this.myXVFB.start();
227
224
  }
228
225
 
229
226
  async runByScript(
@@ -237,24 +234,7 @@ export class Engine {
237
234
  name = name[0];
238
235
  }
239
236
  const storageManager = new StorageManager(name, options);
240
- let engineDelegate;
241
- switch (options.browser) {
242
- case 'firefox': {
243
- engineDelegate = new Firefox(storageManager, options);
244
- break;
245
- }
246
- case 'chrome': {
247
- engineDelegate = new Chromium(storageManager, options);
248
- break;
249
- }
250
- case 'edge': {
251
- engineDelegate = new Chromium(storageManager, options);
252
- break;
253
- }
254
- case 'safari': {
255
- engineDelegate = new Safari(storageManager, options);
256
- }
257
- }
237
+ const engineDelegate = this._createEngineDelegate(storageManager, options);
258
238
 
259
239
  let preScripts, postScripts, postURLScripts, pageCompleteCheck;
260
240
 
@@ -331,7 +311,41 @@ export class Engine {
331
311
  }
332
312
 
333
313
  const extras = await engineDelegate.getHARs();
314
+ this._processHARResults(collector, extras, options);
334
315
 
316
+ const totalResult = collector.getResults();
317
+ this._backfillBrowserInfo(totalResult, extras, options);
318
+
319
+ if (!options.enableProfileRun) {
320
+ logResultLogLine(totalResult);
321
+ }
322
+
323
+ await this._attachMetadata(
324
+ totalResult,
325
+ failures,
326
+ errorsOutsideTheBrowser,
327
+ options
328
+ );
329
+
330
+ return totalResult;
331
+ }
332
+
333
+ _createEngineDelegate(storageManager, options) {
334
+ switch (options.browser) {
335
+ case 'firefox': {
336
+ return new Firefox(storageManager, options);
337
+ }
338
+ case 'chrome':
339
+ case 'edge': {
340
+ return new Chromium(storageManager, options);
341
+ }
342
+ case 'safari': {
343
+ return new Safari(storageManager, options);
344
+ }
345
+ }
346
+ }
347
+
348
+ _processHARResults(collector, extras, options) {
335
349
  // Backfill the fully loaded data that we extract from the HAR
336
350
  // Only do this if we actually had requests
337
351
  if (
@@ -357,8 +371,9 @@ export class Engine {
357
371
  collector.addMainDocumentTimings(timing.url, timing.timings);
358
372
  }
359
373
  }
374
+ }
360
375
 
361
- const totalResult = collector.getResults();
376
+ _backfillBrowserInfo(totalResult, extras, options) {
362
377
  // Add extra fields to the HAR
363
378
  // to make the HAR files better when we use them in
364
379
  // compare.sitespeed.io
@@ -371,14 +386,53 @@ export class Engine {
371
386
  result.info.browser.name = extras.har.log.browser.name;
372
387
  result.info.browser.version = extras.har.log.browser.version;
373
388
  if (options.browser === 'firefox' && options.firefox) {
374
- result.info.browser.args = options.firefox.args;
389
+ // Issue #1622 — surface the full set of args/preferences/binary
390
+ // that browsertime applied to Firefox, not just what the user
391
+ // passed via --firefox.args / --firefox.preference. Captured by
392
+ // configureBuilder; see lib/firefox/webdriver/builder.js.
393
+ if (options.recordedBrowserSettings) {
394
+ const recorded = options.recordedBrowserSettings;
395
+ result.info.browser.args = recorded.args;
396
+ result.info.browser.preferences = recorded.preferences;
397
+ if (recorded.binary) {
398
+ result.info.browser.binary = recorded.binary;
399
+ }
400
+ if (recorded.profile) {
401
+ result.info.browser.profile = recorded.profile;
402
+ }
403
+ if (recorded.androidPackage) {
404
+ result.info.browser.androidPackage = recorded.androidPackage;
405
+ }
406
+ if (recorded.androidActivity) {
407
+ result.info.browser.androidActivity = recorded.androidActivity;
408
+ }
409
+ } else {
410
+ result.info.browser.args = options.firefox.args;
411
+ result.info.browser.preference = options.firefox.preference;
412
+ }
375
413
  if (options.firefox.geckoProfiler === true) {
376
414
  result.info.browser.geckProfilerFeatures =
377
415
  options.firefox.geckoProfilerParams.features;
378
416
  }
379
- result.info.browser.preference = options.firefox.preference;
380
- } else if (options.browser === 'chrome') {
381
- if (options.chrome) {
417
+ } else if (options.browser === 'chrome' || options.browser === 'edge') {
418
+ // Issue #1622 surface the full set of args/preferences/etc that
419
+ // browsertime applied to the browser, not just what the user passed
420
+ // via --chrome.args. Captured by setupChromiumOptions; see
421
+ // lib/chrome/webdriver/setupChromiumOptions.js.
422
+ if (options.recordedBrowserSettings) {
423
+ const recorded = options.recordedBrowserSettings;
424
+ result.info.browser.args = recorded.args;
425
+ result.info.browser.preferences = recorded.preferences;
426
+ if (recorded.mobileEmulation) {
427
+ result.info.browser.mobileEmulation = recorded.mobileEmulation;
428
+ }
429
+ if (recorded.binaryPath) {
430
+ result.info.browser.binaryPath = recorded.binaryPath;
431
+ }
432
+ if (recorded.extensions > 0) {
433
+ result.info.browser.extensions = recorded.extensions;
434
+ }
435
+ } else if (options.chrome) {
382
436
  result.info.browser.args = options.chrome.args;
383
437
  }
384
438
  if (
@@ -433,11 +487,14 @@ export class Engine {
433
487
  }
434
488
  }
435
489
  }
490
+ }
436
491
 
437
- if (!options.enableProfileRun) {
438
- logResultLogLine(totalResult);
439
- }
440
-
492
+ async _attachMetadata(
493
+ totalResult,
494
+ failures,
495
+ errorsOutsideTheBrowser,
496
+ options
497
+ ) {
441
498
  if (failures.length > 0) {
442
499
  // If we have a result
443
500
  if (totalResult[0]) {
@@ -475,7 +532,6 @@ export class Engine {
475
532
  };
476
533
  }
477
534
  }
478
- return totalResult;
479
535
  }
480
536
 
481
537
  async run(url, scriptsByCategory, asyncScriptsByCategory) {