browsertime 27.2.0 → 27.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 +20 -0
- package/bin/browsertime.js +31 -5
- package/bin/browsertimeWebPageReplay.js +1 -1
- package/lib/chrome/coverage.js +198 -0
- package/lib/chrome/webdriver/chromium.js +14 -0
- package/lib/core/engine/collector.js +8 -0
- package/lib/core/engine/command/measure.js +4 -5
- package/lib/core/engine/commands.js +14 -26
- package/lib/core/engine/context.js +4 -9
- package/lib/core/engine/index.js +6 -2
- package/lib/core/seleniumRunner.js +1 -2
- package/lib/safari/safariInspectorClient.js +0 -1
- package/lib/screenshot/index.js +1 -1
- package/lib/support/browserScript.js +2 -4
- package/lib/support/cli.js +56 -21
- package/lib/support/har/index.js +6 -3
- package/lib/support/helpTopics.js +174 -0
- package/lib/support/util.js +46 -0
- package/lib/video/postprocessing/finetune/addTextToVideo.js +1 -1
- package/package.json +16 -15
- package/scripting.d.ts +21 -0
- package/types/android/index.d.ts +2 -2
- package/types/android/index.d.ts.map +1 -1
- package/types/core/engine/command/measure.d.ts +6 -6
- package/types/core/engine/command/measure.d.ts.map +1 -1
- package/types/core/engine/command/simpleperf.d.ts +1 -1
- package/types/core/engine/command/simpleperf.d.ts.map +1 -1
- package/types/core/engine/commands.d.ts +51 -48
- package/types/core/engine/commands.d.ts.map +1 -1
- package/types/core/engine/context.d.ts +9 -8
- package/types/core/engine/context.d.ts.map +1 -1
- package/types/support/tcpdump.d.ts +1 -1
- package/types/support/tcpdump.d.ts.map +1 -1
- package/types/support/util.d.ts +5 -0
- package/types/support/util.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/convertFps.d.ts +1 -1
- package/types/video/postprocessing/finetune/convertFps.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/removeOrange.d.ts +1 -1
- package/types/video/postprocessing/finetune/removeOrange.d.ts.map +1 -1
- package/types/video/postprocessing/visualmetrics/visualMetrics.d.ts +1 -1
- package/types/video/postprocessing/visualmetrics/visualMetrics.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/convert.d.ts +1 -1
- package/types/video/screenRecording/desktop/convert.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/desktopRecorder.d.ts +2 -2
- package/types/video/screenRecording/desktop/ffmpegRecorder.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/osx/getScreen.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getScreen.d.ts.map +1 -1
- package/types/video/screenRecording/iosSimulator/convertToMp4.d.ts +1 -1
- package/types/video/screenRecording/iosSimulator/convertToMp4.d.ts.map +1 -1
- package/types/video/screenRecording/iosSimulator/recorder.d.ts +2 -6
- package/types/video/screenRecording/iosSimulator/recorder.d.ts.map +1 -1
- package/types/scripting.d.ts +0 -2
package/lib/core/engine/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { arch as _arch } from 'node:os';
|
|
2
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
|
-
import merge from 'lodash.merge';
|
|
4
3
|
import { execaCommand as command } from 'execa';
|
|
5
4
|
import { StorageManager } from '../../support/storageManager.js';
|
|
6
5
|
import { Firefox } from '../../firefox/webdriver/firefox.js';
|
|
@@ -10,7 +9,12 @@ import {
|
|
|
10
9
|
addConnectivity,
|
|
11
10
|
removeConnectivity
|
|
12
11
|
} from '../../connectivity/index.js';
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
logResultLogLine,
|
|
14
|
+
getProperty,
|
|
15
|
+
toArray,
|
|
16
|
+
merge
|
|
17
|
+
} from '../../support/util.js';
|
|
14
18
|
import {
|
|
15
19
|
getFullyLoaded,
|
|
16
20
|
getMainDocumentTimings,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { Condition } from 'selenium-webdriver';
|
|
4
3
|
import { isAndroidConfigured } from '../android/index.js';
|
|
5
4
|
import { UrlLoadError, BrowserError, TimeoutError } from '../support/errors.js';
|
|
@@ -8,7 +7,7 @@ import { getViewPort } from '../support/getViewPort.js';
|
|
|
8
7
|
import { defaultPageCompleteCheck } from './pageCompleteChecks/defaultPageCompleteCheck.js';
|
|
9
8
|
import { pageCompleteCheckByInactivity } from './pageCompleteChecks/pageCompleteCheckByInactivity.js';
|
|
10
9
|
import { spaInactivity as spaCheck } from './pageCompleteChecks/spaInactivity.js';
|
|
11
|
-
import { getProperty } from '../support/util.js';
|
|
10
|
+
import { getProperty, merge } from '../support/util.js';
|
|
12
11
|
const log = getLogger('browsertime');
|
|
13
12
|
|
|
14
13
|
const defaults = {
|
package/lib/screenshot/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
4
3
|
import { screenshotDefaults } from './defaults.js';
|
|
5
4
|
import {
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
saveJpg
|
|
9
8
|
} from '../support/images/index.js';
|
|
10
9
|
import { loadCustomJimp } from './loadCustomJimp.js';
|
|
10
|
+
import { merge } from '../support/util.js';
|
|
11
11
|
|
|
12
12
|
const SCREENSHOT_DIR = 'screenshots';
|
|
13
13
|
const log = getLogger('browsertime.screenshot');
|
|
@@ -141,8 +141,7 @@ async function findAndParseScripts(root, category) {
|
|
|
141
141
|
const categoryName = path.basename(dirPath);
|
|
142
142
|
|
|
143
143
|
return scriptsFromDirectory(dirPath).then(scripts => {
|
|
144
|
-
const categories = {};
|
|
145
|
-
categories[categoryName] = scripts;
|
|
144
|
+
const categories = { [categoryName]: scripts };
|
|
146
145
|
return categories;
|
|
147
146
|
});
|
|
148
147
|
}
|
|
@@ -154,8 +153,7 @@ async function findAndParseScripts(root, category) {
|
|
|
154
153
|
let scripts = {};
|
|
155
154
|
|
|
156
155
|
scripts[name] = generateScriptObject(name, root, content);
|
|
157
|
-
let categories = {};
|
|
158
|
-
categories[category] = scripts;
|
|
156
|
+
let categories = { [category]: scripts };
|
|
159
157
|
|
|
160
158
|
return categories;
|
|
161
159
|
})
|
package/lib/support/cli.js
CHANGED
|
@@ -14,6 +14,12 @@ import { geckoProfilerDefaults } from '../firefox/settings/geckoProfilerDefaults
|
|
|
14
14
|
import { findUpSync } from './fileUtil.js';
|
|
15
15
|
import { setProperty, getProperty } from './util.js';
|
|
16
16
|
import { execaSync } from 'execa';
|
|
17
|
+
import {
|
|
18
|
+
classifyHelp,
|
|
19
|
+
captureTopics,
|
|
20
|
+
applyHelpFilter,
|
|
21
|
+
buildEpilog
|
|
22
|
+
} from './helpTopics.js';
|
|
17
23
|
|
|
18
24
|
const configPath = findUpSync(['.browsertime.json']);
|
|
19
25
|
|
|
@@ -152,7 +158,16 @@ export function parseCommandLine() {
|
|
|
152
158
|
arg === '--android' ? '--android.enabled' : arg
|
|
153
159
|
);
|
|
154
160
|
|
|
155
|
-
|
|
161
|
+
// Detect --help <topic> / --help-all up front and rewrite them to a
|
|
162
|
+
// bare --help so yargs still triggers its normal help+exit flow.
|
|
163
|
+
const rawArgs = hideBin(argvFix);
|
|
164
|
+
const help = classifyHelp(rawArgs);
|
|
165
|
+
|
|
166
|
+
let yargsInstance = yargs(help.args);
|
|
167
|
+
// Wrap .option() to record each option's key under its inline
|
|
168
|
+
// `group: '<topic>'` field. Browsertime already annotates every option
|
|
169
|
+
// with a group, so this gives us a topic -> keys map for free.
|
|
170
|
+
const topicKeys = captureTopics(yargsInstance);
|
|
156
171
|
let validated = yargsInstance
|
|
157
172
|
.parserConfiguration({
|
|
158
173
|
'camel-case-expansion': false,
|
|
@@ -358,6 +373,20 @@ export function parseCommandLine() {
|
|
|
358
373
|
'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.',
|
|
359
374
|
group: 'chrome'
|
|
360
375
|
})
|
|
376
|
+
.option('chrome.coverage', {
|
|
377
|
+
type: 'boolean',
|
|
378
|
+
default: false,
|
|
379
|
+
describe:
|
|
380
|
+
'Collect JavaScript and CSS code coverage on every iteration. ' +
|
|
381
|
+
'Per-iteration summaries with a per-file breakdown land in the ' +
|
|
382
|
+
'per-URL result as coverage.js[] and coverage.css[]. This has a ' +
|
|
383
|
+
'real performance cost: detailed JS coverage causes V8 to ' +
|
|
384
|
+
'deoptimize and will skew timing metrics like LCP, TBT and INP. ' +
|
|
385
|
+
'Use --enableProfileRun instead if you want coverage without ' +
|
|
386
|
+
'affecting your timing data - it collects coverage during the ' +
|
|
387
|
+
'extra profile iteration only.',
|
|
388
|
+
group: 'chrome'
|
|
389
|
+
})
|
|
361
390
|
.option('cpu', {
|
|
362
391
|
type: 'boolean',
|
|
363
392
|
describe:
|
|
@@ -636,7 +665,7 @@ export function parseCommandLine() {
|
|
|
636
665
|
.option('enableProfileRun', {
|
|
637
666
|
type: 'boolean',
|
|
638
667
|
describe:
|
|
639
|
-
'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.'
|
|
668
|
+
'Make one extra run that collects the profiling trace log (no other metrics is collected). For Chrome it will collect the timeline trace and JavaScript/CSS coverage, 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.'
|
|
640
669
|
})
|
|
641
670
|
.option('enableVideoRun', {
|
|
642
671
|
type: 'boolean',
|
|
@@ -923,76 +952,76 @@ export function parseCommandLine() {
|
|
|
923
952
|
type: 'boolean',
|
|
924
953
|
default: false,
|
|
925
954
|
describe: 'Save one screenshot per iteration.',
|
|
926
|
-
group: '
|
|
955
|
+
group: 'screenshot'
|
|
927
956
|
})
|
|
928
957
|
.option('screenshotLCP', {
|
|
929
958
|
type: 'boolean',
|
|
930
959
|
default: false,
|
|
931
960
|
describe:
|
|
932
961
|
'Save one screenshot per iteration that shows the largest contentful paint element (if the browser supports LCP).',
|
|
933
|
-
group: '
|
|
962
|
+
group: 'screenshot'
|
|
934
963
|
})
|
|
935
964
|
.option('screenshotLS', {
|
|
936
965
|
type: 'boolean',
|
|
937
966
|
default: false,
|
|
938
967
|
describe:
|
|
939
968
|
'Save one screenshot per iteration that shows the layout shift elements (if the browser supports layout shift).',
|
|
940
|
-
group: '
|
|
969
|
+
group: 'screenshot'
|
|
941
970
|
})
|
|
942
971
|
.option('screenshotParams.type', {
|
|
943
972
|
describe: 'Set the file type of the screenshot',
|
|
944
973
|
choices: ['png', 'jpg'],
|
|
945
974
|
default: screenshotDefaults.type,
|
|
946
|
-
group: '
|
|
975
|
+
group: 'screenshot'
|
|
947
976
|
})
|
|
948
977
|
.option('screenshotParams.jpg.quality', {
|
|
949
978
|
describe: 'Quality of the JPEG screenshot. 1-100',
|
|
950
979
|
default: screenshotDefaults.jpg.quality,
|
|
951
|
-
group: '
|
|
980
|
+
group: 'screenshot'
|
|
952
981
|
})
|
|
953
982
|
.option('screenshotParams.maxSize', {
|
|
954
983
|
describe: 'The max size of the screenshot (width and height).',
|
|
955
984
|
default: screenshotDefaults.maxSize,
|
|
956
|
-
group: '
|
|
985
|
+
group: 'screenshot'
|
|
957
986
|
})
|
|
958
987
|
.option('pageCompleteCheck', {
|
|
959
988
|
describe:
|
|
960
989
|
'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',
|
|
961
|
-
group: '
|
|
990
|
+
group: 'pageload'
|
|
962
991
|
})
|
|
963
992
|
.option('pageCompleteWaitTime', {
|
|
964
993
|
describe:
|
|
965
994
|
'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.',
|
|
966
995
|
default: 8000,
|
|
967
|
-
group: '
|
|
996
|
+
group: 'pageload'
|
|
968
997
|
})
|
|
969
998
|
.option('pageCompleteCheckInactivity', {
|
|
970
999
|
describe:
|
|
971
1000
|
'Alternative way to choose when to end your test. This will wait for 2 seconds of inactivity that happens after loadEventEnd.',
|
|
972
1001
|
type: 'boolean',
|
|
973
1002
|
default: false,
|
|
974
|
-
group: '
|
|
1003
|
+
group: 'pageload'
|
|
975
1004
|
})
|
|
976
1005
|
.option('pageCompleteCheckNetworkIdle', {
|
|
977
1006
|
describe:
|
|
978
1007
|
'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 ',
|
|
979
1008
|
type: 'boolean',
|
|
980
1009
|
default: false,
|
|
981
|
-
group: '
|
|
1010
|
+
group: 'pageload'
|
|
982
1011
|
})
|
|
983
1012
|
.option('pageCompleteCheckPollTimeout', {
|
|
984
1013
|
type: 'number',
|
|
985
1014
|
default: 1500,
|
|
986
1015
|
describe:
|
|
987
1016
|
'The time in ms to wait for running the page complete check the next time.',
|
|
988
|
-
group: '
|
|
1017
|
+
group: 'pageload'
|
|
989
1018
|
})
|
|
990
1019
|
.option('pageCompleteCheckStartWait', {
|
|
991
1020
|
type: 'number',
|
|
992
1021
|
default: 5000,
|
|
993
1022
|
describe:
|
|
994
1023
|
'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',
|
|
995
|
-
group: '
|
|
1024
|
+
group: 'pageload'
|
|
996
1025
|
})
|
|
997
1026
|
.option('pageLoadStrategy', {
|
|
998
1027
|
type: 'string',
|
|
@@ -1000,7 +1029,7 @@ export function parseCommandLine() {
|
|
|
1000
1029
|
choices: ['eager', 'none', 'normal'],
|
|
1001
1030
|
describe:
|
|
1002
1031
|
'Set the strategy to waiting for document readiness after a navigation event. After the strategy is ready, your pageCompleteCheck will start running.',
|
|
1003
|
-
group: '
|
|
1032
|
+
group: 'pageload'
|
|
1004
1033
|
})
|
|
1005
1034
|
.option('iterations', {
|
|
1006
1035
|
alias: 'n',
|
|
@@ -1025,14 +1054,14 @@ export function parseCommandLine() {
|
|
|
1025
1054
|
default: 0,
|
|
1026
1055
|
describe:
|
|
1027
1056
|
'Extra time added for the browser to settle before starting to test a URL. This delay happens after the browser was opened and before the navigation to the URL',
|
|
1028
|
-
group: '
|
|
1057
|
+
group: 'pageload'
|
|
1029
1058
|
})
|
|
1030
1059
|
.option('webdriverPageload', {
|
|
1031
1060
|
type: 'boolean',
|
|
1032
1061
|
describe:
|
|
1033
1062
|
'Use webdriver.get to initialize the page load instead of window.location.',
|
|
1034
1063
|
default: false,
|
|
1035
|
-
group: '
|
|
1064
|
+
group: 'pageload'
|
|
1036
1065
|
})
|
|
1037
1066
|
.option('proxy.pac', {
|
|
1038
1067
|
type: 'string',
|
|
@@ -1168,7 +1197,7 @@ export function parseCommandLine() {
|
|
|
1168
1197
|
'Use internal browser functionality to clear browser cache between runs instead of only using Selenium.',
|
|
1169
1198
|
type: 'boolean',
|
|
1170
1199
|
default: false,
|
|
1171
|
-
group: '
|
|
1200
|
+
group: 'pageload'
|
|
1172
1201
|
})
|
|
1173
1202
|
.option('basicAuth', {
|
|
1174
1203
|
describe:
|
|
@@ -1310,7 +1339,7 @@ export function parseCommandLine() {
|
|
|
1310
1339
|
default: false,
|
|
1311
1340
|
describe:
|
|
1312
1341
|
'Flush DNS between runs, works on Mac OS and Linux. Your user needs sudo rights to be able to flush the DNS.',
|
|
1313
|
-
group: '
|
|
1342
|
+
group: 'pageload'
|
|
1314
1343
|
})
|
|
1315
1344
|
.option('extension', {
|
|
1316
1345
|
describe:
|
|
@@ -1321,7 +1350,7 @@ export function parseCommandLine() {
|
|
|
1321
1350
|
'Convenient parameter to use if you test a SPA application: will automatically wait for X seconds after last network activity and use hash in file names. Read more: https://www.sitespeed.io/documentation/sitespeed.io/spa/',
|
|
1322
1351
|
type: 'boolean',
|
|
1323
1352
|
default: false,
|
|
1324
|
-
group: '
|
|
1353
|
+
group: 'pageload'
|
|
1325
1354
|
})
|
|
1326
1355
|
.option('cjs', {
|
|
1327
1356
|
describe:
|
|
@@ -1334,7 +1363,7 @@ export function parseCommandLine() {
|
|
|
1334
1363
|
default: 3,
|
|
1335
1364
|
describe:
|
|
1336
1365
|
'If the browser fails to start, you can retry to start it this amount of times.',
|
|
1337
|
-
group: '
|
|
1366
|
+
group: 'pageload'
|
|
1338
1367
|
})
|
|
1339
1368
|
.option('preWarmServer', {
|
|
1340
1369
|
type: 'boolean',
|
|
@@ -1363,8 +1392,14 @@ export function parseCommandLine() {
|
|
|
1363
1392
|
.alias('V', 'version')
|
|
1364
1393
|
.alias('v', 'verbose')
|
|
1365
1394
|
.wrap(yargsInstance.terminalWidth())
|
|
1395
|
+
.epilog(buildEpilog(help.mode, help.topic, [...topicKeys.keys()]))
|
|
1366
1396
|
.check(validateInput);
|
|
1367
1397
|
|
|
1398
|
+
// Filter the visible options based on the requested help mode. This
|
|
1399
|
+
// must happen before .argv is read because that's what triggers
|
|
1400
|
+
// yargs to render help and exit.
|
|
1401
|
+
applyHelpFilter(yargsInstance, help.mode, help.topic, topicKeys);
|
|
1402
|
+
|
|
1368
1403
|
let argv = validated.argv;
|
|
1369
1404
|
|
|
1370
1405
|
if (
|
package/lib/support/har/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { pathToFolder } from '../pathToFolder.js';
|
|
4
3
|
import { localTime } from '../util.js';
|
|
5
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
6
5
|
const log = getLogger('browsertime');
|
|
7
6
|
const require = createRequire(import.meta.url);
|
|
8
7
|
const version = require('../../../package.json').version;
|
|
9
|
-
import { pick, isEmpty, getProperty } from '../../support/util.js';
|
|
8
|
+
import { pick, isEmpty, getProperty, merge } from '../../support/util.js';
|
|
10
9
|
|
|
11
10
|
const sensitiveHeaders = new Set([
|
|
12
11
|
'authorization',
|
|
@@ -43,6 +42,7 @@ function getDocumentRequests(entries, pageId) {
|
|
|
43
42
|
|
|
44
43
|
do {
|
|
45
44
|
entry = pageEntries.shift();
|
|
45
|
+
if (!entry) break;
|
|
46
46
|
requests.push(entry);
|
|
47
47
|
} while (entry.response.redirectURL);
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ function getFinalURL(entries, pageref) {
|
|
|
53
53
|
const requests = getDocumentRequests(entries, pageref);
|
|
54
54
|
|
|
55
55
|
const finalEntry = requests.pop();
|
|
56
|
-
return finalEntry.request.url;
|
|
56
|
+
return finalEntry ? finalEntry.request.url : undefined;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function addExtrasToHAR(
|
|
@@ -250,13 +250,16 @@ export function getMainDocumentTimings(har) {
|
|
|
250
250
|
for (let page of har.log.pages) {
|
|
251
251
|
const pageId = page.id;
|
|
252
252
|
const url = page._url;
|
|
253
|
+
if (url === undefined) continue;
|
|
253
254
|
|
|
254
255
|
let pageEntries = [...entries];
|
|
255
256
|
const finalURL = getFinalURL(pageEntries, pageId);
|
|
257
|
+
if (finalURL === undefined) continue;
|
|
256
258
|
|
|
257
259
|
pageEntries = pageEntries.filter(
|
|
258
260
|
entry => entry.pageref === pageId && entry.request.url === finalURL
|
|
259
261
|
);
|
|
262
|
+
if (pageEntries.length === 0) continue;
|
|
260
263
|
|
|
261
264
|
timings.push({ url, timings: pageEntries[0].timings });
|
|
262
265
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*eslint no-console: 0*/
|
|
2
|
+
|
|
3
|
+
// Help-topic dispatch for the Browsertime CLI.
|
|
4
|
+
//
|
|
5
|
+
// Browsertime declares ~250 options in one big yargs chain, with each
|
|
6
|
+
// option carrying an inline `group: '<topic>'` field. The unfiltered
|
|
7
|
+
// --help dump is overwhelming; this module lets users say:
|
|
8
|
+
//
|
|
9
|
+
// browsertime --help -> curated set of common options
|
|
10
|
+
// browsertime --help <topic> -> only that topic
|
|
11
|
+
// browsertime --help-all -> the full historical dump
|
|
12
|
+
//
|
|
13
|
+
// We intercept the yargs instance's .option() call to capture each
|
|
14
|
+
// key's topic from the inline `group` field, so the cli.js chain
|
|
15
|
+
// doesn't have to change shape.
|
|
16
|
+
|
|
17
|
+
// Curated set of "everyday" options the default --help should show.
|
|
18
|
+
// Keep this list short -- anything missing is one --help <topic> away.
|
|
19
|
+
const COMMON_KEYS = new Set([
|
|
20
|
+
// common everyday flags
|
|
21
|
+
'browser',
|
|
22
|
+
'b',
|
|
23
|
+
'iterations',
|
|
24
|
+
'n',
|
|
25
|
+
'video',
|
|
26
|
+
'visualMetrics',
|
|
27
|
+
'speedIndex',
|
|
28
|
+
'docker',
|
|
29
|
+
'preURL',
|
|
30
|
+
'headless',
|
|
31
|
+
'connectivity.profile',
|
|
32
|
+
'c',
|
|
33
|
+
'connectivity.engine',
|
|
34
|
+
'mobile',
|
|
35
|
+
'resultDir',
|
|
36
|
+
'cpu',
|
|
37
|
+
// meta
|
|
38
|
+
'config',
|
|
39
|
+
'help',
|
|
40
|
+
'h',
|
|
41
|
+
'verbose',
|
|
42
|
+
'v',
|
|
43
|
+
'version',
|
|
44
|
+
'V'
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
// Meta-flags that always stay visible regardless of mode.
|
|
48
|
+
const ALWAYS_KEEP = new Set([
|
|
49
|
+
'help',
|
|
50
|
+
'h',
|
|
51
|
+
'version',
|
|
52
|
+
'V',
|
|
53
|
+
'config',
|
|
54
|
+
'verbose',
|
|
55
|
+
'v'
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const DOCS_URL = 'https://www.sitespeed.io/documentation/browsertime/';
|
|
59
|
+
|
|
60
|
+
// Parse the raw arg list, detect help intent, and return a cleaned arg
|
|
61
|
+
// list with `--help-all` / `--help <topic>` collapsed to a plain
|
|
62
|
+
// `--help` so yargs still triggers its normal help+exit flow.
|
|
63
|
+
//
|
|
64
|
+
// Returns { mode, topic, args } where mode is one of:
|
|
65
|
+
// 'none' -> no --help passed
|
|
66
|
+
// 'default' -> plain --help, show the curated common subset
|
|
67
|
+
// 'topic' -> --help <topic>, show only that topic
|
|
68
|
+
// 'all' -> --help-all (or --help all), show everything
|
|
69
|
+
export function classifyHelp(rawArgs) {
|
|
70
|
+
const cleaned = [];
|
|
71
|
+
let mode = 'none';
|
|
72
|
+
let topic;
|
|
73
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
74
|
+
const a = rawArgs[i];
|
|
75
|
+
if (a === '--help-all') {
|
|
76
|
+
mode = 'all';
|
|
77
|
+
cleaned.push('--help');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (a === '--help' || a === '-h' || a === '--help=true') {
|
|
81
|
+
const next = rawArgs[i + 1];
|
|
82
|
+
if (next && !next.startsWith('-')) {
|
|
83
|
+
if (next === 'all') {
|
|
84
|
+
mode = 'all';
|
|
85
|
+
i++;
|
|
86
|
+
} else {
|
|
87
|
+
mode = 'topic';
|
|
88
|
+
topic = next;
|
|
89
|
+
i++;
|
|
90
|
+
}
|
|
91
|
+
} else if (mode === 'none') {
|
|
92
|
+
mode = 'default';
|
|
93
|
+
}
|
|
94
|
+
cleaned.push('--help');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
cleaned.push(a);
|
|
98
|
+
}
|
|
99
|
+
return { mode, topic, args: cleaned };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Wrap a yargs instance so every subsequent .option(key, def) call also
|
|
103
|
+
// records `key` under `def.group` in the returned Map. Browsertime's
|
|
104
|
+
// option chain already carries inline `group: '<topic>'` annotations,
|
|
105
|
+
// so no chain edits are needed.
|
|
106
|
+
export function captureTopics(yargsInstance) {
|
|
107
|
+
const topicKeys = new Map();
|
|
108
|
+
const originalOption = yargsInstance.option.bind(yargsInstance);
|
|
109
|
+
yargsInstance.option = function (key, def) {
|
|
110
|
+
if (def && def.group) {
|
|
111
|
+
const list = topicKeys.get(def.group) || [];
|
|
112
|
+
list.push(key);
|
|
113
|
+
topicKeys.set(def.group, list);
|
|
114
|
+
}
|
|
115
|
+
return originalOption(key, def);
|
|
116
|
+
};
|
|
117
|
+
return topicKeys;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Build the epilog (footer) shown under --help, varying by mode.
|
|
121
|
+
export function buildEpilog(mode, topic, topicNames) {
|
|
122
|
+
if (mode === 'all' || mode === 'none') {
|
|
123
|
+
return `Read the docs at ${DOCS_URL}`;
|
|
124
|
+
}
|
|
125
|
+
if (mode === 'topic') {
|
|
126
|
+
return [
|
|
127
|
+
`Showing only the "${topic}" options.`,
|
|
128
|
+
`Run with --help-all for the full reference, or --help to see common options.`,
|
|
129
|
+
`Read the docs at ${DOCS_URL}`
|
|
130
|
+
].join('\n');
|
|
131
|
+
}
|
|
132
|
+
// default
|
|
133
|
+
const topics = topicNames.join(', ');
|
|
134
|
+
return [
|
|
135
|
+
'Topics (use `browsertime --help <topic>`):',
|
|
136
|
+
' ' + topics,
|
|
137
|
+
'',
|
|
138
|
+
'Run with --help-all to see every option, or read the docs at ' + DOCS_URL
|
|
139
|
+
].join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Apply the topic filter by calling yargs.hide() on every key not in
|
|
143
|
+
// the set we want to display. Returns true if filtering was applied.
|
|
144
|
+
export function applyHelpFilter(yargsInstance, mode, topic, topicKeys) {
|
|
145
|
+
if (mode === 'none' || mode === 'all') return false;
|
|
146
|
+
|
|
147
|
+
let keep;
|
|
148
|
+
if (mode === 'topic') {
|
|
149
|
+
if (!topicKeys.has(topic)) {
|
|
150
|
+
console.error(`Unknown help topic: ${topic}`);
|
|
151
|
+
console.error('Available topics: ' + [...topicKeys.keys()].join(', '));
|
|
152
|
+
// CLI dispatch: bailing out here is intentional (deliberate "no
|
|
153
|
+
// such topic" exit code), not an error to bubble up the stack.
|
|
154
|
+
// eslint-disable-next-line unicorn/no-process-exit
|
|
155
|
+
process.exit(2);
|
|
156
|
+
}
|
|
157
|
+
// Topic mode = just the topic. No COMMON_KEYS floor here -- topic
|
|
158
|
+
// views should focus on the topic, not re-render common stuff
|
|
159
|
+
// that's one --help away.
|
|
160
|
+
keep = new Set(topicKeys.get(topic));
|
|
161
|
+
} else {
|
|
162
|
+
// default
|
|
163
|
+
keep = COMMON_KEYS;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const allKeys = Object.keys(yargsInstance.getOptions().key);
|
|
167
|
+
for (const k of allKeys) {
|
|
168
|
+
if (ALWAYS_KEEP.has(k)) continue;
|
|
169
|
+
if (!keep.has(k)) {
|
|
170
|
+
yargsInstance.hide(k);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
package/lib/support/util.js
CHANGED
|
@@ -388,3 +388,49 @@ export function getProperty(object, path, defaultValue) {
|
|
|
388
388
|
|
|
389
389
|
return result === undefined ? defaultValue : result;
|
|
390
390
|
}
|
|
391
|
+
|
|
392
|
+
function isPlainObject(value) {
|
|
393
|
+
if (value === null || typeof value !== 'object') return false;
|
|
394
|
+
const proto = Object.getPrototypeOf(value);
|
|
395
|
+
return proto === Object.prototype || proto === null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function canMergeInto(value) {
|
|
399
|
+
return value !== null && typeof value === 'object';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function mergeInto(target, source) {
|
|
403
|
+
for (const key of Object.keys(source)) {
|
|
404
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const sourceValue = source[key];
|
|
408
|
+
const targetValue = target[key];
|
|
409
|
+
if (Array.isArray(sourceValue)) {
|
|
410
|
+
const into = Array.isArray(targetValue) ? targetValue : [];
|
|
411
|
+
mergeInto(into, sourceValue);
|
|
412
|
+
target[key] = into;
|
|
413
|
+
} else if (isPlainObject(sourceValue)) {
|
|
414
|
+
const into = canMergeInto(targetValue) ? targetValue : {};
|
|
415
|
+
mergeInto(into, sourceValue);
|
|
416
|
+
target[key] = into;
|
|
417
|
+
} else if (sourceValue === undefined) {
|
|
418
|
+
if (!(key in target)) target[key] = undefined;
|
|
419
|
+
} else {
|
|
420
|
+
target[key] = sourceValue;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* A replacement for lodash.merge(target, ...sources).
|
|
427
|
+
*
|
|
428
|
+
*/
|
|
429
|
+
export function merge(target, ...sources) {
|
|
430
|
+
if (target === undefined || target === null) return target;
|
|
431
|
+
for (const source of sources) {
|
|
432
|
+
if (source === undefined || source === null) continue;
|
|
433
|
+
mergeInto(target, source);
|
|
434
|
+
}
|
|
435
|
+
return target;
|
|
436
|
+
}
|
|
@@ -45,7 +45,7 @@ export async function addTextToVideo(
|
|
|
45
45
|
|
|
46
46
|
arguments_.push(
|
|
47
47
|
'-vf',
|
|
48
|
-
`drawtext=${fontFile}x=w/2-(tw/2): y=H-h/10:fontcolor=white:fontsize=h/${fontSize}:box=1:boxcolor=0x000000AA:text='%{pts
|
|
48
|
+
String.raw`drawtext=${fontFile}x=w/2-(tw/2): y=H-h/10:fontcolor=white:fontsize=h/${fontSize}:box=1:boxcolor=0x000000AA:text='%{pts\:hms}'${allTimingMetrics}`,
|
|
49
49
|
'-y',
|
|
50
50
|
outputFile
|
|
51
51
|
);
|
package/package.json
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browsertime",
|
|
3
3
|
"description": "Get performance metrics from your web page using Browsertime.",
|
|
4
|
-
"version": "27.
|
|
4
|
+
"version": "27.4.0",
|
|
5
5
|
"bin": "./bin/browsertime.js",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"types": "./
|
|
7
|
+
"types": "./scripting.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@devicefarmer/adbkit": "3.3.8",
|
|
10
|
-
"@sitespeed.io/chromedriver": "
|
|
11
|
-
"@sitespeed.io/edgedriver": "
|
|
12
|
-
"@sitespeed.io/geckodriver": "0.36.
|
|
13
|
-
"@sitespeed.io/log": "
|
|
10
|
+
"@sitespeed.io/chromedriver": "148.0.7778",
|
|
11
|
+
"@sitespeed.io/edgedriver": "148.0.3967",
|
|
12
|
+
"@sitespeed.io/geckodriver": "0.36.2",
|
|
13
|
+
"@sitespeed.io/log": "2.0.0",
|
|
14
14
|
"@sitespeed.io/throttle": "6.0.0",
|
|
15
|
-
"chrome-har": "1.3.
|
|
16
|
-
"chrome-remote-interface": "0.
|
|
15
|
+
"chrome-har": "1.3.1",
|
|
16
|
+
"chrome-remote-interface": "0.34.0",
|
|
17
17
|
"execa": "9.6.1",
|
|
18
18
|
"fast-stats": "0.0.7",
|
|
19
19
|
"ff-test-bidi-har-export": "0.0.22",
|
|
20
|
-
"
|
|
21
|
-
"selenium-webdriver": "4.43.0",
|
|
20
|
+
"selenium-webdriver": "4.44.0",
|
|
22
21
|
"yargs": "18.0.0"
|
|
23
22
|
},
|
|
24
23
|
"optionalDependencies": {
|
|
@@ -26,13 +25,14 @@
|
|
|
26
25
|
"usb-power-profiling": "1.6.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
28
|
+
"@eslint/js": "10.0.1",
|
|
29
29
|
"@types/selenium-webdriver": "4.35.5",
|
|
30
30
|
"ava": "6.4.1",
|
|
31
31
|
"clean-jsdoc-theme": "4.3.2",
|
|
32
|
-
"eslint": "
|
|
32
|
+
"eslint": "10.4.0",
|
|
33
33
|
"eslint-config-prettier": "10.1.8",
|
|
34
34
|
"eslint-plugin-prettier": "5.5.5",
|
|
35
|
-
"eslint-plugin-unicorn": "
|
|
35
|
+
"eslint-plugin-unicorn": "64.0.0",
|
|
36
36
|
"jsdoc": "4.0.5",
|
|
37
37
|
"prettier": "3.8.3",
|
|
38
38
|
"serve": "14.2.6",
|
|
@@ -55,13 +55,14 @@
|
|
|
55
55
|
"index.js",
|
|
56
56
|
"lib",
|
|
57
57
|
"package.json",
|
|
58
|
+
"scripting.d.ts",
|
|
58
59
|
"vendor",
|
|
59
60
|
"types"
|
|
60
61
|
],
|
|
61
62
|
"exports": {
|
|
62
63
|
".": {
|
|
63
|
-
"
|
|
64
|
-
"
|
|
64
|
+
"types": "./scripting.d.ts",
|
|
65
|
+
"import": "./index.js"
|
|
65
66
|
}
|
|
66
67
|
},
|
|
67
68
|
"scripts": {
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
"lint:fix": "eslint . --fix",
|
|
73
74
|
"tsc": "tsc",
|
|
74
75
|
"jsdoc": "jsdoc --configure jsdoc/jsdoc.json",
|
|
75
|
-
"prepublishOnly": "npm run lint && npm run tsc"
|
|
76
|
+
"prepublishOnly": "npm run lint && npm run tsc && npx -y publint"
|
|
76
77
|
},
|
|
77
78
|
"author": "Peter Hedenskog",
|
|
78
79
|
"contributors": [
|