forked from sitespeedio/browsertime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.js
More file actions
1452 lines (1416 loc) · 52.9 KB
/
cli.js
File metadata and controls
1452 lines (1416 loc) · 52.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { format } from 'node:util';
import { readFileSync, statSync } from 'node:fs';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import {
framerate,
crf,
addTimer,
threads,
xvfbDisplay
} from '../video/defaults.js';
import { screenshotDefaults } from '../screenshot/defaults.js';
import { geckoProfilerDefaults } from '../firefox/settings/geckoProfilerDefaults.js';
import { findUpSync } from './fileUtil.js';
import { setProperty, getProperty } from './util.js';
import { execaSync } from 'execa';
const configPath = findUpSync(['.browsertime.json']);
let config;
try {
config = configPath ? JSON.parse(readFileSync(configPath)) : {};
} catch (error) {
if (error instanceof SyntaxError) {
/* eslint no-console: off */
console.error(
'Could not parse the config JSON file ' +
configPath +
'. Is the file really valid JSON?'
);
}
throw error;
}
function hasbin(bin) {
try {
const cmd = process.platform === 'win32' ? 'where' : 'which';
execaSync(cmd, bin);
return true;
} catch {
return false;
}
}
function validateInput(argv) {
// Check NodeJS major version
const fullVersion = process.versions.node;
const minVersion = 20;
const majorVersion = fullVersion.split('.')[0];
if (majorVersion < minVersion) {
return (
'You need to have at least NodeJS version ' +
minVersion +
' to run Browsertime. You are using version ' +
fullVersion
);
}
// validate URLs/files
const urlOrFiles = argv._;
for (let urlOrFile of urlOrFiles) {
if (!urlOrFile.startsWith('http')) {
// is existing file?
try {
statSync(urlOrFile);
} catch {
return format(
"'%s' does not exist, is the path to the file correct?",
urlOrFile
);
}
}
}
if (argv.chrome && argv.chrome.mobileEmulation) {
const m = argv.chrome.mobileEmulation;
if (!(m.deviceName || (m.height && m.width && m.pixelRatio))) {
return 'chrome.mobileEmulation needs to specify deviceName OR height, width and pixelRatio';
}
}
if ((argv.video || argv.visualMetrics) && !hasbin(['ffmpeg'])) {
return 'You need to have ffmpeg in your path to be able to record a video.';
}
if (argv.safari && argv.safari.useSimulator && !argv.safari.deviceUDID) {
return 'You need to specify the --safari.deviceUDID when you run the simulator.';
}
if (argv.debug && argv.browser === 'safari') {
return 'Debug mode do not work in Safari. Please try with Firefox/Chrome or Edge';
}
if (argv.debug && argv.android.enabled) {
return 'Debug mode do not work on Android. Please run debug mode on Desktop.';
}
if (argv.debug && argv.docker) {
return 'There is no benefit running debug mode inside a Docker container.';
}
if (argv.tcpdump && !hasbin(['tcpdump'])) {
return 'You need to have tcpdump in your path to be able to record a tcpdump.';
}
if (argv.gnirehtet && !hasbin(['gnirehtet'])) {
return 'You need to have gnirehtet in your path to be able to use it. If you run on a Mac you can install it using Homebrew: brew install gnirehtet';
}
if (
argv.connectivity &&
argv.connectivity.engine === 'humble' &&
(!argv.connectivity.humble || !argv.connectivity.humble.url)
) {
return 'You need to specify the URL to Humble by using the --connectivity.humble.url option.';
}
if (
argv.firefox &&
((argv.firefox.nightly && argv.firefox.beta) ||
(argv.firefox.nightly && argv.firefox.developer) ||
(argv.firefox.developer && argv.firefox.beta))
) {
return 'You can only run one Firefox instance at a time.';
}
if (
argv.connectivity.profile !== 'custom' &&
(argv.connectivity.upstreamKbps ||
argv.connectivity.downstreamKbps ||
argv.connectivity.latency)
) {
return 'You must pass --connectivity.profile "custom" for custom connectivity configs to take effect.';
}
if (Array.isArray(argv.iterations)) {
return 'Ooops you passed number of iterations twice, remove one of them and try again.';
}
if (argv.urlAlias) {
if (!Array.isArray(argv.urlAlias)) argv.urlAlias = [argv.urlAlias];
if (argv._.length !== argv.urlAlias.length) {
return `Mismatch between number of URLs (${argv._.length}) and alias (${argv.urlAlias.length}). You need to provide the same amount of alias as URLs.`;
}
}
return true;
}
export function parseCommandLine() {
let argvFix = process.argv.map(arg =>
arg === '--android' ? '--android.enabled' : arg
);
let yargsInstance = yargs(hideBin(argvFix));
let validated = yargsInstance
.parserConfiguration({
'camel-case-expansion': false,
'deep-merge-config': true
})
.env('BROWSERTIME')
.usage('$0 [options] <url>/<scriptFile>')
.require(1, 'One or more url or script files')
.option('timeouts.browserStart', {
default: 60_000,
type: 'number',
describe: 'Timeout when waiting for browser to start, in milliseconds',
group: 'timeouts'
})
.option('timeouts.pageLoad', {
default: 300_000,
type: 'number',
describe: 'Timeout when waiting for url to load, in milliseconds',
group: 'timeouts'
})
.option('timeouts.script', {
default: 120_000,
type: 'number',
describe: 'Timeout when running browser scripts, in milliseconds',
group: 'timeouts'
})
.option('timeouts.pageCompleteCheck', {
alias: 'maxLoadTime',
default: 120_000,
type: 'number',
describe:
'Timeout when waiting for page to complete loading, in milliseconds',
group: 'timeouts'
})
.option('timeouts.networkIdle', {
default: 5000,
type: 'number',
describe:
'Timeout when running pageCompleteCheckNetworkIdle, in milliseconds',
group: 'timeouts'
})
.option('chrome.args', {
describe:
'Extra command line arguments to pass to the Chrome process (e.g. --no-sandbox). ' +
'To add multiple arguments to Chrome, repeat --chrome.args once per argument.',
group: 'chrome'
})
.option('chrome.binaryPath', {
describe:
'Path to custom Chrome binary (e.g. Chrome Canary). ' +
'On OS X, the path should be to the binary inside the app bundle, ' +
'e.g. "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"',
group: 'chrome'
})
.option('chrome.chromedriverPath', {
describe:
"Path to custom ChromeDriver binary. Make sure to use a ChromeDriver version that's compatible with " +
"the version of Chrome you're using",
group: 'chrome'
})
.option('chrome.chromedriverPort', {
type: 'number',
describe: 'Specify "--port" args for chromedriver prcocess',
group: 'chrome'
})
.option('chrome.mobileEmulation.deviceName', {
describe:
"Name of device to emulate. Works only standalone (see list in Chrome DevTools, but add phone like 'iPhone 6'). This will override your userAgent string.",
group: 'chrome'
})
.option('chrome.mobileEmulation.width', {
type: 'number',
describe: 'Width in pixels of emulated mobile screen (e.g. 360)',
group: 'chrome'
})
.option('chrome.mobileEmulation.height', {
type: 'number',
describe: 'Height in pixels of emulated mobile screen (e.g. 640)',
group: 'chrome'
})
.option('chrome.mobileEmulation.pixelRatio', {
describe: 'Pixel ratio of emulated mobile screen (e.g. 2.0)',
group: 'chrome'
})
.option('chrome.android.package', {
describe:
'Run Chrome on your Android device. Set to com.android.chrome for default Chrome version. You need to have adb installed to make this work.',
group: 'chrome'
})
.option('chrome.android.activity', {
describe: 'Name of the Activity hosting the WebView.',
group: 'chrome'
})
.option('chrome.android.process', {
describe:
'Process name of the Activity hosting the WebView. If not given, the process name is assumed to be the same as chrome.android.package.',
group: 'chrome'
})
.option('chrome.android.deviceSerial', {
describe:
'Choose which device to use. If you do not set it, first device will be used.',
group: 'chrome'
})
.option('chrome.traceCategories', {
describe:
'A comma separated list of Tracing event categories to include in the Trace log. Default no trace categories is collected.',
type: 'string',
group: 'chrome'
})
.option('chrome.traceCategory', {
describe:
'Add a trace category to the default ones. Use --chrome.traceCategory multiple times if you want to add multiple categories. Example: --chrome.traceCategory disabled-by-default-v8.cpu_profiler',
type: 'string',
group: 'chrome'
})
.option('chrome.enableTraceScreenshots', {
alias: 'enableTraceScreenshots',
describe:
'Include screenshots in the trace log (enabling the trace category disabled-by-default-devtools.screenshot).',
type: 'boolean',
group: 'chrome'
})
.option('chrome.enableChromeDriverLog', {
describe: 'Log Chromedriver communication to a log file.',
type: 'boolean',
group: 'chrome'
})
.option('chrome.enableVerboseChromeDriverLog', {
describe: 'Log verboose Chromedriver communication to a log file.',
type: 'boolean',
group: 'chrome'
})
.option('chrome.enableVideoAutoplay', {
describe: 'Allow videos to autoplay.',
type: 'boolean',
group: 'chrome'
})
.option('chrome.timeline', {
alias: 'chrome.trace',
describe:
'Collect the timeline data. Drag and drop the JSON in your Chrome detvools timeline panel or check out the CPU metrics in the Browsertime.json',
type: 'boolean',
group: 'chrome'
})
.option('chrome.timelineExtras', {
describe:
'If you collect the timeline using --chrome.timeline or --enableProfileRun this will add some extra timings and tracks to your timeline.',
type: 'boolean',
default: true,
group: 'chrome'
})
.option('chrome.timelineRecordingType', {
alias: 'chrome.traceRecordingType',
describe: 'Expose the start/stop commands for the chrome trace',
default: 'pageload',
choices: ['pageload', 'custom'],
type: 'string',
group: 'chrome'
})
.option('chrome.collectPerfLog', {
type: 'boolean',
describe:
'Collect performance log from Chrome with Page and Network events and save to disk.',
group: 'chrome'
})
.option('chrome.collectNetLog', {
type: 'boolean',
describe: 'Collect network log from Chrome and save to disk.',
group: 'chrome'
})
.option('chrome.netLogCaptureMode', {
default: 'IncludeSensitive',
choices: ['Default', 'IncludeSensitive', 'Everything'],
describe: 'Choose capture mode for Chromes netlog.',
group: 'chrome'
})
.option('chrome.collectConsoleLog', {
type: 'boolean',
describe: 'Collect Chromes console log and save to disk.',
group: 'chrome'
})
.option('chrome.appendToUserAgent', {
type: 'string',
describe: 'Append to the user agent.',
group: 'chrome'
})
.option('chrome.noDefaultOptions', {
type: 'boolean',
describe:
'Prevent Browsertime from setting its default options for Chrome',
group: 'chrome'
})
.option('chrome.cleanUserDataDir', {
type: 'boolean',
describe:
'If you use --user-data-dir as an argument to Chrome and want to clean that directory between each iteration you should use --chrome.cleanUserDataDir true.',
group: 'chrome'
})
.option('cpu', {
type: 'boolean',
describe:
'Easy way to enable both chrome.timeline for Chrome and geckoProfile for Firefox'
})
.option('android.powerTesting', {
alias: 'androidPower',
type: 'boolean',
describe:
'Enables android power testing - charging must be disabled for this.' +
'(You have to disable charging yourself for this - it depends on the phone model).',
group: 'android'
})
.option('android.usbPowerTesting', {
alias: 'androidUsbPower',
type: 'boolean',
describe:
'Enables android power testing using usb-power-profiling. Assumes that ' +
'a valid device is attached to the phone. See here for supported devices: ' +
'https://github.com/fqueze/usb-power-profiling?tab=readme-ov-file#supported-devices',
group: 'android'
})
.option('chrome.CPUThrottlingRate', {
type: 'number',
describe:
'Enables CPU throttling to emulate slow CPUs. Throttling rate as a slowdown factor (1 is no throttle, 2 is 2x slowdown, etc)',
group: 'chrome'
})
.option('chrome.includeResponseBodies', {
describe: 'Include response bodies in the HAR file.',
default: 'none',
choices: ['none', 'all', 'html'],
group: 'chrome'
})
.option('chrome.cdp.performance', {
type: 'boolean',
default: true,
describe:
'Collect Chrome perfromance metrics from Chrome DevTools Protocol',
group: 'chrome'
})
.option('chrome.blockDomainsExcept', {
alias: 'blockDomainsExcept',
describe:
'Block all domains except this domain. Use it multiple time to keep multiple domains. You can also wildcard domains like *.sitespeed.io. Use this when you wanna block out all third parties.',
group: 'chrome'
})
.option('chrome.ignoreCertificateErrors', {
type: 'boolean',
default: true,
describe: 'Make Chrome ignore certificate errors. Defaults to true.',
group: 'chrome'
})
.option('firefox.binaryPath', {
describe:
'Path to custom Firefox binary (e.g. Firefox Nightly). ' +
'On OS X, the path should be to the binary inside the app bundle, ' +
'e.g. /Applications/Firefox.app/Contents/MacOS/firefox-bin',
group: 'firefox'
})
.option('firefox.geckodriverPath', {
describe:
"Path to custom geckodriver binary. Make sure to use a geckodriver version that's compatible with " +
"the version of Firefox (Gecko) you're using",
group: 'firefox'
})
.option('firefox.geckodriverArgs', {
describe:
'Flags passed in to Geckodriver see https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html. Use it like --firefox.geckodriverArgs="--marionette-port" --firefox.geckodriverArgs=1027 ',
type: 'string',
group: 'firefox'
})
.option('firefox.appendToUserAgent', {
type: 'string',
describe: 'Append to the user agent.',
group: 'firefox'
})
.option('firefox.nightly', {
describe:
'Use Firefox Nightly. Works on OS X. For Linux you need to set the binary path.',
type: 'boolean',
group: 'firefox'
})
.option('firefox.beta', {
describe:
'Use Firefox Beta. Works on OS X. For Linux you need to set the binary path.',
type: 'boolean',
group: 'firefox'
})
.option('firefox.developer', {
describe:
'Use Firefox Developer. Works on OS X. For Linux you need to set the binary path.',
type: 'boolean',
group: 'firefox'
})
.option('firefox.preference', {
describe:
'Extra command line arguments to pass Firefox preferences by the format key:value ' +
'To add multiple preferences, repeat --firefox.preference once per argument.',
group: 'firefox'
})
.option('firefox.args', {
describe:
'Extra command line arguments to pass to the Firefox process (e.g. --MOZ_LOG). ' +
'To add multiple arguments to Firefox, repeat --firefox.args once per argument.',
group: 'firefox'
})
/* -- Remove from the help until we have the new Geckodriver > 0.26
.option('firefox.env', {
describe:
'Extra environment variables to set in the format name=value. ' +
'To add multiple environment variables, repeat --firefox.env once per environment variable.',
group: 'firefox'
})
*/
.option('firefox.includeResponseBodies', {
describe: 'Include response bodies in HAR',
default: 'none',
choices: ['none', 'all', 'html'],
group: 'firefox'
})
.option('firefox.appconstants', {
describe: 'Include Firefox AppConstants information in the results',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.acceptInsecureCerts', {
describe: 'Accept insecure certs',
type: 'boolean',
group: 'firefox'
})
.option('firefox.windowRecorder', {
describe:
'Use the internal compositor-based Firefox window recorder to emit PNG files for each ' +
'frame that is a meaningful change. The PNG output will further be merged into a ' +
'variable frame rate video for analysis. Use this instead of ffmpeg to record a video (you still need the --video flag).',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.memoryReport', {
describe: 'Measure firefox resident memory after each iteration.',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.memoryReportParams.minizeFirst', {
describe:
'Force a collection before dumping and measuring the memory report.',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.geckoProfiler', {
describe: 'Collect a profile using the internal gecko profiler',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.geckoProfilerRecordingType', {
describe: 'Expose the start/stop commands for the gecko profiler',
default: 'pageload',
choices: ['pageload', 'custom'],
type: 'string',
group: 'firefox'
})
.option('firefox.geckoProfilerParams.features', {
describe: 'Enabled features during gecko profiling',
default: geckoProfilerDefaults.features,
type: 'string',
group: 'firefox'
})
.option('firefox.geckoProfilerParams.threads', {
describe: 'Threads to profile.',
default: geckoProfilerDefaults.threads,
type: 'string',
group: 'firefox'
})
.option('firefox.geckoProfilerParams.interval', {
describe: `Sampling interval in ms. Defaults to ${geckoProfilerDefaults.desktopSamplingInterval} on desktop, and ${geckoProfilerDefaults.androidSamplingInterval} on android.`,
type: 'number',
group: 'firefox'
})
.option('firefox.geckoProfilerParams.bufferSize', {
describe: 'Buffer size in elements. Default is ~90MB.',
default: geckoProfilerDefaults.bufferSize,
type: 'number',
group: 'firefox'
})
.option('firefox.perfStats', {
describe:
'Collect gecko performance statistics as measured internally by the firefox browser. See https://searchfox.org/mozilla-central/source/tools/performance/PerfStats.h#24-33',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.perfStatsParams.features', {
describe:
'Comma-separated list of PerfStats features to enable. If not provided, all features will be enabled.',
type: 'string',
group: 'firefox'
})
.option('firefox.collectMozLog', {
type: 'boolean',
describe:
'Collect the MOZ HTTP log (by default). See --firefox.setMozLog if you ' +
'need to specify the logs you wish to gather.',
group: 'firefox'
})
.option('firefox.powerConsumption', {
type: 'boolean',
default: false,
describe:
'Enable power consumption collection (in Wh). To get the consumption you also need to set firefox.geckoProfilerParams.features to include power.',
group: 'firefox'
})
.option('firefox.setMozLog', {
describe:
'Use in conjunction with firefox.collectMozLog to set MOZ_LOG to something ' +
'specific. Without this, the HTTP logs will be collected by default',
default:
'timestamp,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5',
group: 'firefox'
})
.option('firefox.noDefaultPrefs', {
describe: 'Prevents browsertime from setting its default preferences.',
default: false,
type: 'boolean',
group: 'firefox'
})
.option('firefox.disableSafeBrowsing', {
describe: 'Disable safebrowsing.',
default: true,
type: 'boolean',
group: 'firefox'
})
.option('firefox.disableTrackingProtection', {
describe: 'Disable Tracking Protection.',
default: true,
type: 'boolean',
group: 'firefox'
})
.option('firefox.android.package', {
describe:
'Run Firefox or a GeckoView-consuming App on your Android device. Set to org.mozilla.geckoview_example for default Firefox version. You need to have adb installed to make this work.',
group: 'firefox'
})
.option('firefox.android.activity', {
describe: 'Name of the Activity hosting the GeckoView.',
group: 'firefox'
})
.option('firefox.android.deviceSerial', {
describe:
'Choose which device to use. If you do not set it, first device will be used.',
group: 'firefox'
})
.option('firefox.android.intentArgument', {
describe:
'Configure how the Android intent is launched. Passed through to `adb shell am start ...`; ' +
'follow the format at https://developer.android.com/studio/command-line/adb#IntentSpec. ' +
'To add multiple arguments, repeat --firefox.android.intentArgument once per argument.',
group: 'firefox'
})
.option('firefox.profileTemplate', {
describe:
'Profile template directory that will be cloned and used as the base of each profile each instance of Firefox is launched against. Use this to pre-populate databases with certificates, tracking protection lists, etc.',
group: 'firefox'
})
.option('selenium.url', {
describe:
'URL to a running Selenium server (e.g. to run a browser on another machine).',
group: 'selenium'
})
.option('enableProfileRun', {
type: 'boolean',
describe:
'Make one extra run that collects the profiling trace log (no other metrics is collected). For Chrome it will collect the timeline trace, for Firefox it will get the Geckoprofiler trace. This means you do not need to get the trace for all runs and can skip the overhead it produces.'
})
.option('enableVideoRun', {
type: 'boolean',
describe:
'Make one extra run that collects video and visual metrics. This means you can do your runs with --visualMetrics true --video false --enableVideoRun true to collect visual metrics from all runs and save a video from the profile/video run. If you run it together with --enableProfileRun it will also collect profiling trace.'
})
.option('video', {
type: 'boolean',
describe:
'Record a video and store the video. Set it to false to remove the video that is created by turning on visualMetrics. To remove fully turn off video recordings, make sure to set video and visualMetrics to false. Requires FFMpeg to be installed.'
})
.option('videoParams.framerate', {
default: framerate,
describe: 'Frames per second',
group: 'video'
})
.option('videoParams.crf', {
default: crf,
describe:
'Constant rate factor see https://trac.ffmpeg.org/wiki/Encode/H.264#crf',
group: 'video'
})
.option('videoParams.addTimer', {
type: 'boolean',
default: addTimer,
describe: 'Add timer and metrics to the video.',
group: 'video'
})
.option('videoParams.debug', {
type: 'boolean',
default: false,
describe:
'Turn on debug to record a video with all pre/post and scripts/URLS you test in one iteration. Visual Metrics will then automatically be disabled.',
group: 'video'
})
.option('videoParams.keepOriginalVideo', {
type: 'boolean',
default: false,
describe:
'Keep the original video. Use it when you have a Visual Metrics bug and want to create an issue at GitHub',
group: 'video'
})
.option('videoParams.thumbsize', {
default: 400,
describe:
'The maximum size of the thumbnail in the filmstrip. Default is 400 pixels in either direction. If videoParams.filmstripFullSize is used that setting overrides this.',
group: 'video'
})
.option('videoParams.filmstripFullSize', {
type: 'boolean',
default: false,
describe:
'Keep original sized screenshots. Will make the run take longer time',
group: 'video'
})
.option('videoParams.filmstripQuality', {
default: 75,
describe: 'The quality of the filmstrip screenshots. 0-100.',
group: 'video'
})
.option('videoParams.createFilmstrip', {
type: 'boolean',
default: true,
describe: 'Create filmstrip screenshots.',
group: 'video'
})
.option('videoParams.nice', {
default: 0,
describe:
'Use nice when running FFMPEG during the run. A value from -20 to 19 https://linux.die.net/man/1/nice',
group: 'video'
})
.option('videoParams.taskset', {
describe:
'Start FFMPEG with taskset -c <CPUS> to pin FFMPG to specific CPU(s). Specify a numerical list of processors. The list may contain multiple items, separated by comma, and ranges. For example, "0,5,7,9-11".',
group: 'video'
})
.option('videoParams.convert', {
type: 'boolean',
default: true,
describe:
'Convert the original video to a viewable format (for most video players). Turn that off to make a faster run.',
group: 'video'
})
.option('videoParams.threads', {
default: threads,
describe:
'Number of threads to use for video recording. Default is determined by ffmpeg.',
group: 'video'
})
.option('visualMetrics', {
type: 'boolean',
describe:
'Collect Visual Metrics like First Visual Change, SpeedIndex, Perceptual Speed Index and Last Visual Change. Requires FFMpeg and Python dependencies'
})
.option('visualElements', {
alias: 'visuaElements',
type: 'boolean',
describe:
'Collect Visual Metrics from elements. Works only with --visualMetrics turned on. By default you will get visual metrics from the largest image within the view port and the largest h1. You can also configure to pickup your own defined elements with --scriptInput.visualElements'
})
.option('visualMetricsPerceptual', {
type: 'boolean',
describe: 'Collect Perceptual Speed Index when you run --visualMetrics.'
})
.option('visualMetricsContentful', {
type: 'boolean',
describe: 'Collect Contentful Speed Index when you run --visualMetrics.'
})
.option('visualMetricsKeyColor', {
type: 'array',
nargs: 8,
describe:
'Collect Key Color frame metrics when you run --visualMetrics. Each --visualMetricsKeyColor supplied must have 8 arguments: key name, red channel (0-255) low and high, green channel (0-255) low and high, blue channel (0-255) low and high, fraction (0.0-1.0) of pixels that must match each channel.'
})
.option('scriptInput.visualElements', {
describe:
'Include specific elements in visual elements. Give the element a name and select it with document.body.querySelector. Use like this: --scriptInput.visualElements name:domSelector see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors. Add multiple instances to measure multiple elements. Visual Metrics will use these elements and calculate when they are visible and fully rendered.'
})
.option('scriptInput.longTask', {
alias: 'minLongTaskLength',
description:
'Set the minimum length of a task to be categorised as a CPU Long Task. It can never be smaller than 50. The value is in ms and only works in Chromium browsers at the moment.',
type: 'number',
default: 50
})
.option('browser', {
alias: 'b',
default: 'chrome',
choices: ['chrome', 'firefox', 'edge', 'safari'],
describe:
'Specify browser. Safari only works on OS X/iOS. Edge only work on OS that supports Edge.'
})
.option('android.ignoreShutdownFailures', {
alias: 'ignoreShutdownFailures',
type: 'boolean',
default: false,
describe: 'If set, shutdown failures will be ignored on Android.',
group: 'android'
})
.option('android', {
type: 'boolean',
default: false,
describe:
'Short key to use Android. Defaults to use com.android.chrome unless --browser is specified.'
})
.option('android.rooted', {
type: 'boolean',
alias: 'androidRooted',
default: false,
describe:
'If your phone is rooted you can use this to set it up following Mozillas best practice for stable metrics.',
group: 'android'
})
.option('android.pinCPUSpeed', {
alias: 'androidPinCPUSpeed',
choices: ['min', 'middle', 'max'],
default: 'min',
describe:
'Using a Samsung A51 or Moto G5 you can choose how to pin the CPU to better align the speed with your users. This only works on rooted phones and together with --android.rooted',
group: 'android'
})
.option('android.batteryTemperatureLimit', {
alias: 'androidBatteryTemperatureLimit',
type: 'integer',
describe:
'Do the battery temperature need to be below a specific limit before we start the test?',
group: 'android'
})
.option('android.batteryTemperatureWaitTimeInSeconds', {
alias: 'androidBatteryTemperatureWaitTimeInSeconds',
type: 'integer',
default: 120,
describe:
'How long time to wait (in seconds) if the androidBatteryTemperatureWaitTimeInSeconds is not met before the next try',
group: 'android'
})
.option('android.batteryTemperatureReboot', {
alias: 'androidBatteryTemperatureReboot',
type: 'boolean',
default: false,
describe:
'If your phone does not get the minimum temperature aftet the wait time, reboot the phone.',
group: 'android'
})
.option('android.pretestPowerPress', {
alias: 'androidPretestPowerPress',
type: 'boolean',
default: false,
describe: 'Press the power button on the phone before a test starts.',
group: 'android'
})
.option('android.pretestPressHomeButton', {
alias: 'androidPretestPressHomeButton',
type: 'boolean',
default: false,
describe: 'Press the home button on the phone before a test starts.',
group: 'android'
})
.option('android.verifyNetwork', {
alias: 'androidVerifyNetwork',
type: 'boolean',
default: false,
describe:
'Before a test start, verify that the device has a Internet connection by pinging 8.8.8.8 (or a configurable domain with --androidPingAddress)',
group: 'android'
})
.option('android.simpleperf', {
alias: 'androidSimpleperf',
type: 'string',
describe: 'Path to the Simpleperf profiler from the Android NDK.',
group: 'android'
})
.option('android.perfettoTrace', {
alias: 'androidPerfettoTrace',
describe: 'Collect a perfetto trace with the given configuration.',
default: false,
group: 'android'
})
/** Process start time. Android-only for now. */
.option('processStartTime', {
type: 'boolean',
default: false,
describe:
'Capture browser process start time (in milliseconds). Android only for now.'
})
.option('edge.edgedriverPath', {
describe:
'Path to custom msedgedriver version (need to match your Edge version).',
group: 'edge'
})
.option('edge.binaryPath', {
describe: 'Path to custom Edge binary',
group: 'edge'
})
.option('safari.ios', {
default: false,
describe:
'Use Safari on iOS. You need to choose browser Safari and iOS to run on iOS.',
type: 'boolean',
group: 'safari'
})
.option('safari.deviceName', {
describe:
'Set the device name. Device names for connected devices are shown in iTunes.',
group: 'safari'
})
.option('safari.deviceUDID', {
describe:
'Set the device UDID. If Xcode is installed, UDIDs for connected devices are available via the output of "xcrun simctl list devices" and in the Device and Simulators window (accessed in Xcode via "Window > Devices and Simulators")',
group: 'safari'
})
.option('safari.deviceType', {
describe:
'Set the device type. If the value of safari:deviceType is `iPhone`, safaridriver will only create a session using an iPhone device or iPhone simulator. If the value of safari:deviceType is `iPad`, safaridriver will only create a session using an iPad device or iPad simulator.',
group: 'safari'
})
.option('safari.useTechnologyPreview', {
type: 'boolean',
default: false,
describe: 'Use Safari Technology Preview',
group: 'safari'
})
.option('safari.diagnose', {
describe:
'When filing a bug report against safaridriver, it is highly recommended that you capture and include diagnostics generated by safaridriver. Diagnostic files are saved to ~/Library/Logs/com.apple.WebDriver/',
group: 'safari'
})
.option('safari.useSimulator', {
describe:
'If the value of useSimulator is true, safaridriver will only use iOS Simulator hosts. If the value of safari:useSimulator is false, safaridriver will not use iOS Simulator hosts. NOTE: An Xcode installation is required in order to run WebDriver tests on iOS Simulator hosts.',
default: false,
type: 'boolean',
group: 'safari'
})
/** Screenshot */
.option('screenshot', {
type: 'boolean',
default: false,
describe: 'Save one screenshot per iteration.',
group: 'Screenshot'
})
.option('screenshotLCP', {
type: 'boolean',
default: false,
describe:
'Save one screenshot per iteration that shows the largest contentful paint element (if the browser supports LCP).',
group: 'Screenshot'
})
.option('screenshotLS', {
type: 'boolean',
default: false,
describe:
'Save one screenshot per iteration that shows the layout shift elements (if the browser supports layout shift).',
group: 'Screenshot'
})
.option('screenshotParams.type', {
describe: 'Set the file type of the screenshot',
choices: ['png', 'jpg'],
default: screenshotDefaults.type,
group: 'Screenshot'
})
.option('screenshotParams.jpg.quality', {
describe: 'Quality of the JPEG screenshot. 1-100',
default: screenshotDefaults.jpg.quality,
group: 'Screenshot'
})
.option('screenshotParams.maxSize', {
describe: 'The max size of the screenshot (width and height).',
default: screenshotDefaults.maxSize,
group: 'Screenshot'
})
.option('pageCompleteCheck', {
describe:
'Supply a JavaScript (inline or JavaScript file) that decides when the browser is finished loading the page and can start to collect metrics. The JavaScript snippet is repeatedly queried to see if page has completed loading (indicated by the script returning true). Use it to fetch timings happening after the loadEventEnd. By default the tests ends 2 seconds after loadEventEnd. Also checkout --pageCompleteCheckInactivity and --pageCompleteCheckPollTimeout',
group: 'PageLoad'
})
.option('pageCompleteWaitTime', {
describe:
'How long time you want to wait for your pageComplteteCheck to finish, after it is signaled to closed. Extra parameter passed on to your pageCompleteCheck.',
default: 8000,
group: 'PageLoad'
})
.option('pageCompleteCheckInactivity', {
describe:
'Alternative way to choose when to end your test. This will wait for 2 seconds of inactivity that happens after loadEventEnd.',
type: 'boolean',
default: false,
group: 'PageLoad'
})
.option('pageCompleteCheckNetworkIdle', {
describe:
'Alternative way to choose when to end your test that works in Chrome and Firefox. Uses CDP or WebDriver Bidi to look at network traffic instead of running JavaScript in the browser to know when to end the test. By default this will wait 5 seconds of inactivity in the network log (no requets/responses in 5 seconds). Use --timeouts.networkIdle to change the 5 seconds. The test will end after 2 minutes if there is still activity on the network. You can change that timout using --timeouts.pageCompleteCheck ',
type: 'boolean',
default: false,
group: 'PageLoad'
})
.option('pageCompleteCheckPollTimeout', {
type: 'number',
default: 1500,
describe:
'The time in ms to wait for running the page complete check the next time.',
group: 'PageLoad'
})
.option('pageCompleteCheckStartWait', {
type: 'number',
default: 5000,
describe:
'The time in ms to wait for running the page complete check for the first time. Use this when you have a pageLoadStrategy set to none',
group: 'PageLoad'
})
.option('pageLoadStrategy', {
type: 'string',
default: 'none',
choices: ['eager', 'none', 'normal'],
describe:
'Set the strategy to waiting for document readiness after a navigation event. After the strategy is ready, your pageCompleteCheck will start running.',
group: 'PageLoad'
})
.option('iterations', {
alias: 'n',
type: 'number',
default: 3,
describe:
'Number of times to test the url (restarting the browser between each test)'
})
.option('prettyPrint', {
type: 'boolean',