browsertime 24.0.0-alpha.2 → 24.0.0-alpha.4
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 +14 -2
- package/bin/browsertime.js +12 -13
- package/lib/android/index.js +16 -6
- package/lib/chrome/longTaskMetrics.js +8 -5
- package/lib/chrome/networkManager.js +7 -3
- package/lib/chrome/webdriver/builder.js +3 -4
- package/lib/chrome/webdriver/chromium.js +13 -4
- package/lib/connectivity/humble.js +2 -2
- package/lib/connectivity/index.js +11 -3
- package/lib/core/engine/collector.js +31 -20
- package/lib/core/engine/command/measure.js +18 -6
- package/lib/core/engine/index.js +6 -3
- package/lib/core/seleniumRunner.js +2 -2
- package/lib/edge/webdriver/builder.js +4 -5
- package/lib/firefox/geckoProfiler.js +7 -7
- package/lib/firefox/networkManager.js +7 -3
- package/lib/firefox/webdriver/builder.js +14 -12
- package/lib/firefox/webdriver/firefox.js +18 -7
- package/lib/support/cli.js +24 -21
- package/lib/support/getViewPort.js +8 -4
- package/lib/support/har/index.js +7 -4
- package/lib/support/statistics.js +8 -7
- package/lib/support/usbPower.js +11 -0
- package/lib/support/util.js +53 -0
- package/lib/support/xvfb.js +4 -4
- package/lib/video/postprocessing/finetune/index.js +3 -3
- package/lib/video/postprocessing/visualmetrics/visualMetrics.js +14 -5
- package/lib/video/screenRecording/android/recorder.js +8 -3
- package/lib/video/screenRecording/desktop/desktopRecorder.js +9 -8
- package/lib/video/video.js +2 -3
- package/package.json +4 -6
- package/types/android/index.d.ts.map +1 -1
- package/types/core/engine/command/measure.d.ts.map +1 -1
- package/types/support/getViewPort.d.ts.map +1 -1
- package/types/support/usbPower.d.ts +2 -0
- package/types/support/usbPower.d.ts.map +1 -0
- package/types/support/util.d.ts +6 -0
- package/types/support/util.d.ts.map +1 -1
- package/types/video/postprocessing/visualmetrics/visualMetrics.d.ts.map +1 -1
- package/types/video/screenRecording/android/recorder.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/desktopRecorder.d.ts.map +1 -1
- package/types/video/video.d.ts.map +1 -1
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
-
import
|
|
2
|
+
import { getProperty } from '../support/util.js';
|
|
3
3
|
const log = getLogger('browsertime.chrome.network');
|
|
4
4
|
|
|
5
5
|
export class NetworkManager {
|
|
6
6
|
constructor(bidi, browsingContextIds, options) {
|
|
7
7
|
this.bidi = bidi;
|
|
8
8
|
this.browsingContextIds = browsingContextIds;
|
|
9
|
-
this.maxTimeout =
|
|
10
|
-
|
|
9
|
+
this.maxTimeout = getProperty(
|
|
10
|
+
options,
|
|
11
|
+
'timeouts.pageCompleteCheck',
|
|
12
|
+
30_000
|
|
13
|
+
);
|
|
14
|
+
this.idleTime = getProperty(options, 'timeouts.networkIdle', 5000);
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
async waitForNetworkIdle() {
|
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
} from 'selenium-webdriver/firefox.js';
|
|
7
7
|
import { getLogger } from '@sitespeed.io/log';
|
|
8
8
|
import { pac, manual } from 'selenium-webdriver/proxy.js';
|
|
9
|
-
import get from 'lodash.get';
|
|
10
9
|
import { defaultFirefoxPreferences } from '../settings/firefoxPreferences.js';
|
|
11
10
|
import { disableSafeBrowsingPreferences } from '../settings/disableSafeBrowsingPreferences.js';
|
|
12
11
|
import { disableTrackingProtectionPreferences } from '../settings/disableTrackingProtectionPreferences.js';
|
|
13
|
-
import { toArray, pick, isEmpty } from '../../support/util.js';
|
|
12
|
+
import { toArray, pick, isEmpty, getProperty } from '../../support/util.js';
|
|
14
13
|
import { Android, isAndroidConfigured } from '../../android/index.js';
|
|
15
14
|
const log = getLogger('browsertime.firefox');
|
|
16
15
|
|
|
@@ -18,7 +17,7 @@ export async function configureBuilder(builder, baseDir, options) {
|
|
|
18
17
|
// Here we configure everything that we need to start Firefox
|
|
19
18
|
const firefoxConfig = options.firefox || {};
|
|
20
19
|
|
|
21
|
-
let geckodriverPath =
|
|
20
|
+
let geckodriverPath = getProperty(firefoxConfig, 'geckodriverPath');
|
|
22
21
|
if (!geckodriverPath) {
|
|
23
22
|
const geckodriver = await import('@sitespeed.io/geckodriver');
|
|
24
23
|
geckodriverPath = geckodriver.default.binPath();
|
|
@@ -35,7 +34,7 @@ export async function configureBuilder(builder, baseDir, options) {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
// https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html
|
|
38
|
-
let geckodriverArguments =
|
|
37
|
+
let geckodriverArguments = getProperty(firefoxConfig, 'geckodriverArgs');
|
|
39
38
|
if (geckodriverArguments) {
|
|
40
39
|
if (!Array.isArray(geckodriverArguments))
|
|
41
40
|
geckodriverArguments = [geckodriverArguments];
|
|
@@ -48,10 +47,10 @@ export async function configureBuilder(builder, baseDir, options) {
|
|
|
48
47
|
|
|
49
48
|
const ffOptions = new Options();
|
|
50
49
|
|
|
51
|
-
const profileTemplate =
|
|
50
|
+
const profileTemplate = getProperty(firefoxConfig, 'profileTemplate');
|
|
52
51
|
if (profileTemplate) {
|
|
53
|
-
log.info('Using profile %s',
|
|
54
|
-
ffOptions.setProfile(
|
|
52
|
+
log.info('Using profile %s', getProperty(firefoxConfig, 'profileTemplate'));
|
|
53
|
+
ffOptions.setProfile(getProperty(firefoxConfig, 'profileTemplate'));
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
if (options.userAgent) {
|
|
@@ -135,10 +134,10 @@ export async function configureBuilder(builder, baseDir, options) {
|
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
let firefoxTypes = [
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
getProperty(firefoxConfig, 'binaryPath') ?? undefined,
|
|
138
|
+
getProperty(firefoxConfig, 'nightly') ? Channel.NIGHTLY : undefined,
|
|
139
|
+
getProperty(firefoxConfig, 'beta') ? Channel.BETA : undefined,
|
|
140
|
+
getProperty(firefoxConfig, 'developer') ? Channel.AURORA : undefined
|
|
142
141
|
];
|
|
143
142
|
|
|
144
143
|
firefoxTypes = firefoxTypes.filter(function (n) {
|
|
@@ -211,7 +210,10 @@ export async function configureBuilder(builder, baseDir, options) {
|
|
|
211
210
|
|
|
212
211
|
builder
|
|
213
212
|
.getCapabilities()
|
|
214
|
-
.set(
|
|
213
|
+
.set(
|
|
214
|
+
'pageLoadStrategy',
|
|
215
|
+
getProperty(options, 'pageLoadStrategy', 'normal')
|
|
216
|
+
);
|
|
215
217
|
|
|
216
218
|
if (isAndroidConfigured(options)) {
|
|
217
219
|
// Monkey patch to avoid changing `selenium-webdriver` before the
|
|
@@ -2,13 +2,15 @@ import { rename as _rename } from 'node:fs';
|
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
|
-
import get from 'lodash.get';
|
|
6
|
-
import usbPowerProfiler from 'usb-power-profiling/usb-power-profiling.js';
|
|
7
5
|
import { adapters } from 'ff-test-bidi-har-export';
|
|
8
6
|
import { getEmptyHAR, mergeHars } from '../../support/har/index.js';
|
|
7
|
+
import { loadUsbPowerProfiler } from '../../support/usbPower.js';
|
|
9
8
|
import { pathToFolder } from '../../support/pathToFolder.js';
|
|
10
9
|
import { isAndroidConfigured, Android } from '../../android/index.js';
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
adjustVisualProgressTimestamps,
|
|
12
|
+
getProperty
|
|
13
|
+
} from '../../support/util.js';
|
|
12
14
|
import { findFiles } from '../../support/fileUtil.js';
|
|
13
15
|
import { GeckoProfiler } from '../geckoProfiler.js';
|
|
14
16
|
import { FirefoxBidi } from '../firefoxBidi.js';
|
|
@@ -75,7 +77,10 @@ export class Firefox {
|
|
|
75
77
|
);
|
|
76
78
|
|
|
77
79
|
if (isAndroidConfigured(this.options) && this.options.androidUsbPower) {
|
|
78
|
-
usbPowerProfiler
|
|
80
|
+
const usbPowerProfiler = await loadUsbPowerProfiler();
|
|
81
|
+
if (usbPowerProfiler) {
|
|
82
|
+
usbPowerProfiler.startSampling();
|
|
83
|
+
}
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -144,7 +149,10 @@ export class Firefox {
|
|
|
144
149
|
if (this.options.androidPower) {
|
|
145
150
|
await this.android.resetPowerUsage();
|
|
146
151
|
} else if (this.options.androidUsbPower) {
|
|
147
|
-
await
|
|
152
|
+
const usbPowerProfiler = await loadUsbPowerProfiler();
|
|
153
|
+
if (usbPowerProfiler) {
|
|
154
|
+
await usbPowerProfiler.resetPowerData();
|
|
155
|
+
}
|
|
148
156
|
}
|
|
149
157
|
}
|
|
150
158
|
|
|
@@ -227,7 +235,7 @@ export class Firefox {
|
|
|
227
235
|
result.memory = await memoryReport.collect(index, url);
|
|
228
236
|
}
|
|
229
237
|
|
|
230
|
-
const useFirefoxAppConstants =
|
|
238
|
+
const useFirefoxAppConstants = getProperty(
|
|
231
239
|
this.options || {},
|
|
232
240
|
'firefox.appconstants',
|
|
233
241
|
false
|
|
@@ -408,7 +416,10 @@ export class Firefox {
|
|
|
408
416
|
if (this.options.androidPower) {
|
|
409
417
|
await this.android.stopPowerTesting();
|
|
410
418
|
} else if (this.options.androidUsbPower) {
|
|
411
|
-
await
|
|
419
|
+
const usbPowerProfiler = await loadUsbPowerProfiler();
|
|
420
|
+
if (usbPowerProfiler) {
|
|
421
|
+
await usbPowerProfiler.stopSampling();
|
|
422
|
+
}
|
|
412
423
|
}
|
|
413
424
|
}
|
|
414
425
|
}
|
package/lib/support/cli.js
CHANGED
|
@@ -2,8 +2,6 @@ import { format } from 'node:util';
|
|
|
2
2
|
import { readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import yargs from 'yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
|
-
import get from 'lodash.get';
|
|
6
|
-
import set from 'lodash.set';
|
|
7
5
|
import {
|
|
8
6
|
framerate,
|
|
9
7
|
crf,
|
|
@@ -14,6 +12,7 @@ import {
|
|
|
14
12
|
import { screenshotDefaults } from '../screenshot/defaults.js';
|
|
15
13
|
import { geckoProfilerDefaults } from '../firefox/settings/geckoProfilerDefaults.js';
|
|
16
14
|
import { findUpSync } from './fileUtil.js';
|
|
15
|
+
import { setProperty, getProperty } from './util.js';
|
|
17
16
|
import { execaSync } from 'execa';
|
|
18
17
|
|
|
19
18
|
const configPath = findUpSync(['.browsertime.json']);
|
|
@@ -1352,13 +1351,13 @@ export function parseCommandLine() {
|
|
|
1352
1351
|
argv.browser = 'firefox';
|
|
1353
1352
|
if (argv.android.enabled) {
|
|
1354
1353
|
// TODO add support for Firefox dev
|
|
1355
|
-
|
|
1354
|
+
setProperty(
|
|
1356
1355
|
argv,
|
|
1357
1356
|
'firefox.android.package',
|
|
1358
1357
|
argv.firefox.nightly ? 'org.mozilla.fenix' : 'org.mozilla.firefox_beta'
|
|
1359
1358
|
);
|
|
1360
1359
|
|
|
1361
|
-
|
|
1360
|
+
setProperty(
|
|
1362
1361
|
argv,
|
|
1363
1362
|
'firefox.android.activity',
|
|
1364
1363
|
'org.mozilla.fenix.IntentReceiverActivity'
|
|
@@ -1367,36 +1366,36 @@ export function parseCommandLine() {
|
|
|
1367
1366
|
}
|
|
1368
1367
|
|
|
1369
1368
|
if (argv.ios) {
|
|
1370
|
-
|
|
1369
|
+
setProperty(argv, 'safari.ios', true);
|
|
1371
1370
|
} else if (argv.android.enabled) {
|
|
1372
1371
|
if (argv.browser === 'chrome') {
|
|
1373
1372
|
// Default to Chrome Android.
|
|
1374
|
-
|
|
1373
|
+
setProperty(
|
|
1375
1374
|
argv,
|
|
1376
1375
|
'chrome.android.package',
|
|
1377
|
-
|
|
1376
|
+
getProperty(argv, 'chrome.android.package', 'com.android.chrome')
|
|
1378
1377
|
);
|
|
1379
1378
|
} else if (argv.browser === 'edge') {
|
|
1380
|
-
|
|
1379
|
+
setProperty(
|
|
1381
1380
|
argv,
|
|
1382
1381
|
'chrome.android.package',
|
|
1383
|
-
|
|
1382
|
+
getProperty(argv, 'chrome.android.package', 'com.microsoft.emmx')
|
|
1384
1383
|
);
|
|
1385
|
-
|
|
1384
|
+
setProperty(
|
|
1386
1385
|
argv,
|
|
1387
1386
|
'chrome.android.activity',
|
|
1388
|
-
|
|
1387
|
+
getProperty(argv, 'chrome.android.activity', 'com.microsoft.ruby.Main')
|
|
1389
1388
|
);
|
|
1390
1389
|
}
|
|
1391
1390
|
}
|
|
1392
1391
|
|
|
1393
1392
|
if (argv.safari && argv.safari.useSimulator) {
|
|
1394
|
-
|
|
1393
|
+
setProperty(argv, 'connectivity.engine', 'throttle');
|
|
1395
1394
|
}
|
|
1396
1395
|
|
|
1397
1396
|
// Always use hash by default when you configure spa
|
|
1398
1397
|
if (argv.spa) {
|
|
1399
|
-
|
|
1398
|
+
setProperty(argv, 'useHash', true);
|
|
1400
1399
|
}
|
|
1401
1400
|
|
|
1402
1401
|
if (argv.urlAlias) {
|
|
@@ -1408,28 +1407,32 @@ export function parseCommandLine() {
|
|
|
1408
1407
|
for (const [index, url] of urls.entries()) {
|
|
1409
1408
|
urlMetaData[url] = argv.urlAlias[index];
|
|
1410
1409
|
}
|
|
1411
|
-
|
|
1410
|
+
setProperty(argv, 'urlMetaData', urlMetaData);
|
|
1412
1411
|
}
|
|
1413
1412
|
|
|
1414
1413
|
// Simplest way to just to get CPU metrics
|
|
1415
1414
|
if (argv.cpu) {
|
|
1416
1415
|
if (argv.browser === 'chrome' || argv.browser === 'edge') {
|
|
1417
|
-
|
|
1416
|
+
setProperty(argv, 'chrome.timeline', true);
|
|
1418
1417
|
} else if (argv.browser === 'firefox') {
|
|
1419
|
-
|
|
1418
|
+
setProperty(argv, 'firefox.geckoProfiler', true);
|
|
1420
1419
|
}
|
|
1421
1420
|
}
|
|
1422
1421
|
|
|
1423
1422
|
if (argv.docker) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1423
|
+
setProperty(argv, 'video', getProperty(argv, 'video', true));
|
|
1424
|
+
setProperty(
|
|
1425
|
+
argv,
|
|
1426
|
+
'visualMetrics',
|
|
1427
|
+
getProperty(argv, 'visualMetrics', true)
|
|
1428
|
+
);
|
|
1426
1429
|
}
|
|
1427
1430
|
|
|
1428
1431
|
// Set the timeouts to a maximum while debugging
|
|
1429
1432
|
if (argv.debug) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
+
setProperty(argv, 'timeouts.pageload', 2_147_483_647);
|
|
1434
|
+
setProperty(argv, 'timeouts.script', 2_147_483_647);
|
|
1435
|
+
setProperty(argv, 'timeouts.pageCompleteCheck', 2_147_483_647);
|
|
1433
1436
|
}
|
|
1434
1437
|
|
|
1435
1438
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import get from 'lodash.get';
|
|
2
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
2
|
import { isAndroidConfigured } from '../android/index.js';
|
|
3
|
+
import { getProperty } from '../support/util.js';
|
|
4
4
|
const log = getLogger('browsertime');
|
|
5
5
|
|
|
6
6
|
// Most use screen size as of 2018
|
|
@@ -40,13 +40,17 @@ export function getViewPort(options) {
|
|
|
40
40
|
if (isAndroidConfigured(options)) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
const emulatedWidth =
|
|
44
|
-
const emulatedHeight =
|
|
43
|
+
const emulatedWidth = getProperty(options, 'chrome.mobileEmulation.width');
|
|
44
|
+
const emulatedHeight = getProperty(options, 'chrome.mobileEmulation.height');
|
|
45
45
|
if (emulatedWidth && emulatedHeight) {
|
|
46
46
|
return `${emulatedWidth}x${emulatedHeight}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const deviceName =
|
|
49
|
+
const deviceName = getProperty(
|
|
50
|
+
options,
|
|
51
|
+
'chrome.mobileEmulation.deviceName',
|
|
52
|
+
false
|
|
53
|
+
);
|
|
50
54
|
if (deviceName) {
|
|
51
55
|
// TODO if it don't map ...
|
|
52
56
|
const viewPort = sizeMap[deviceName];
|
package/lib/support/har/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import merge from 'lodash.merge';
|
|
3
|
-
import get from 'lodash.get';
|
|
4
3
|
import { pathToFolder } from '../pathToFolder.js';
|
|
5
4
|
import { localTime } from '../util.js';
|
|
6
5
|
import { getLogger } from '@sitespeed.io/log';
|
|
7
6
|
const log = getLogger('browsertime');
|
|
8
7
|
const require = createRequire(import.meta.url);
|
|
9
8
|
const version = require('../../../package.json').version;
|
|
10
|
-
import { pick, isEmpty } from '../../support/util.js';
|
|
9
|
+
import { pick, isEmpty, getProperty } from '../../support/util.js';
|
|
11
10
|
|
|
12
11
|
function generateUniquePageId(baseId, existingIdMap) {
|
|
13
12
|
let newId = baseId;
|
|
@@ -119,8 +118,12 @@ function addExtrasToHAR(
|
|
|
119
118
|
|
|
120
119
|
function addMetaToHAR(index, harPage, url, browserScript, options) {
|
|
121
120
|
const _meta = (harPage._meta = {});
|
|
122
|
-
_meta.connectivity =
|
|
123
|
-
_meta.connectivity =
|
|
121
|
+
_meta.connectivity = getProperty(options, 'connectivity.profile', 'native');
|
|
122
|
+
_meta.connectivity = getProperty(
|
|
123
|
+
options,
|
|
124
|
+
'connectivity.alias',
|
|
125
|
+
_meta.connectivity
|
|
126
|
+
);
|
|
124
127
|
|
|
125
128
|
if (options.resultURL) {
|
|
126
129
|
const base = options.resultURL.endsWith('/')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import get from 'lodash.get';
|
|
2
|
-
import set from 'lodash.set';
|
|
3
1
|
import { Stats } from 'fast-stats';
|
|
4
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
|
+
|
|
4
|
+
import { getProperty, setProperty } from './util.js';
|
|
5
|
+
|
|
5
6
|
const log = getLogger('browsertime');
|
|
6
7
|
|
|
7
8
|
function validateType(value, type, message) {
|
|
@@ -29,9 +30,9 @@ function isNumeric(n) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function addNumber(target, key, value) {
|
|
32
|
-
let stats =
|
|
33
|
+
let stats = getProperty(target, key, new Stats());
|
|
33
34
|
stats.push(value);
|
|
34
|
-
|
|
35
|
+
setProperty(target, key, stats);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export class Statistics {
|
|
@@ -128,7 +129,7 @@ export class Statistics {
|
|
|
128
129
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
129
130
|
return Object.keys(this.data).reduce((results, key) => {
|
|
130
131
|
let decimals = options.decimals || 0;
|
|
131
|
-
let stats =
|
|
132
|
+
let stats = getProperty(this.data, [key]);
|
|
132
133
|
if (options.iqr && stats.median() !== 0) {
|
|
133
134
|
// https://en.wikipedia.org/wiki/Interquartile_range
|
|
134
135
|
stats = stats.iqr();
|
|
@@ -147,7 +148,7 @@ export class Statistics {
|
|
|
147
148
|
let name = percentileName(p);
|
|
148
149
|
node[name] = Number.parseFloat(stats.percentile(p).toFixed(decimals));
|
|
149
150
|
}
|
|
150
|
-
|
|
151
|
+
setProperty(results, [key], node);
|
|
151
152
|
|
|
152
153
|
return results;
|
|
153
154
|
}, {});
|
|
@@ -199,7 +200,7 @@ export class Statistics {
|
|
|
199
200
|
|
|
200
201
|
function summarizeRecursive(target, keyPrefix, data) {
|
|
201
202
|
if (data instanceof Stats) {
|
|
202
|
-
|
|
203
|
+
setProperty(target, keyPrefix, summarize(data));
|
|
203
204
|
} else if (typeof data === 'object') {
|
|
204
205
|
for (const key of Object.keys(data)) {
|
|
205
206
|
const path = [...keyPrefix, key];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function loadUsbPowerProfiler() {
|
|
2
|
+
try {
|
|
3
|
+
// usb-power-profiling/usb-power-profiling.js exports a default, so we destructure it
|
|
4
|
+
const { default: usbPowerProfiler } = await import(
|
|
5
|
+
'usb-power-profiling/usb-power-profiling.js'
|
|
6
|
+
);
|
|
7
|
+
return usbPowerProfiler;
|
|
8
|
+
} catch {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/lib/support/util.js
CHANGED
|
@@ -335,3 +335,56 @@ function groupBy(array, property) {
|
|
|
335
335
|
}
|
|
336
336
|
return grouped;
|
|
337
337
|
}
|
|
338
|
+
|
|
339
|
+
export function setProperty(object, path, value) {
|
|
340
|
+
if (typeof path === 'string') {
|
|
341
|
+
path = path.split('.');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let current = object;
|
|
349
|
+
|
|
350
|
+
for (let index = 0; index < path.length - 1; index++) {
|
|
351
|
+
const key = path[index];
|
|
352
|
+
|
|
353
|
+
if (current[key] === undefined) {
|
|
354
|
+
current[key] = {};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
current = current[key];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
current[path.at(-1)] = value;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* A replacement for lodash.get(object, path, [defaultValue]).
|
|
365
|
+
*
|
|
366
|
+
*/
|
|
367
|
+
export function getProperty(object, path, defaultValue) {
|
|
368
|
+
// eslint-disable-next-line unicorn/no-null
|
|
369
|
+
if (object == null) {
|
|
370
|
+
return defaultValue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!Array.isArray(path)) {
|
|
374
|
+
path = path
|
|
375
|
+
.replaceAll(/\[(.*?)\]/g, '.$1')
|
|
376
|
+
.split('.')
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let result = object;
|
|
381
|
+
for (const key of path) {
|
|
382
|
+
// eslint-disable-next-line unicorn/no-null
|
|
383
|
+
if (result == null) {
|
|
384
|
+
return defaultValue;
|
|
385
|
+
}
|
|
386
|
+
result = result[key];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result === undefined ? defaultValue : result;
|
|
390
|
+
}
|
package/lib/support/xvfb.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
|
-
import get from 'lodash.get';
|
|
3
2
|
import { xvfbDisplay } from '../video/defaults.js';
|
|
4
3
|
import { getViewPort } from '../support/getViewPort.js';
|
|
4
|
+
import { getProperty } from '../support/util.js';
|
|
5
5
|
import { isAndroidConfigured } from '../android/index.js';
|
|
6
6
|
|
|
7
7
|
function buildXvfbCommand({ display, screen = 0, size, silent }) {
|
|
@@ -29,7 +29,7 @@ export async function startXvfb({ size, options }) {
|
|
|
29
29
|
xvfbSize = `${Number(width) + extraSizeInFirefox}x${height}`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const display =
|
|
32
|
+
const display = getProperty(options, 'xvfbParams.display', xvfbDisplay);
|
|
33
33
|
const silent = options.verbose >= 2 ? false : true;
|
|
34
34
|
|
|
35
35
|
const { command, args } = buildXvfbCommand({
|
|
@@ -43,7 +43,7 @@ export async function startXvfb({ size, options }) {
|
|
|
43
43
|
detached: true
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
const waitToSettle =
|
|
46
|
+
const waitToSettle = getProperty(options, 'xvfbParams.waitToSettle', 500);
|
|
47
47
|
await new Promise(resolve => {
|
|
48
48
|
setTimeout(resolve, waitToSettle);
|
|
49
49
|
});
|
|
@@ -76,7 +76,7 @@ export class XVFB {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async start() {
|
|
79
|
-
const useXvfb =
|
|
79
|
+
const useXvfb = getProperty(this.options, 'xvfb', false);
|
|
80
80
|
|
|
81
81
|
if (
|
|
82
82
|
(useXvfb === true || useXvfb === 'true') &&
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
} from 'node:fs';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { getLogger } from '@sitespeed.io/log';
|
|
9
|
-
import get from 'lodash.get';
|
|
10
9
|
import { addTextToVideo } from './addTextToVideo.js';
|
|
11
10
|
import { removeOrange } from './removeOrange.js';
|
|
12
11
|
import { convert } from './convertFps.js';
|
|
13
12
|
import { convert as _convert } from '../../defaults.js';
|
|
14
13
|
import { isAndroidConfigured } from '../../../android/index.js';
|
|
14
|
+
import { getProperty } from '../../../support/util.js';
|
|
15
15
|
const rename = promisify(_rename);
|
|
16
16
|
const copyFile = promisify(_copyFile);
|
|
17
17
|
const unlink = promisify(_unlink);
|
|
@@ -28,7 +28,7 @@ export async function finetuneVideo(
|
|
|
28
28
|
const newStart = videoMetrics.videoRecordingStart / 1000;
|
|
29
29
|
let temporaryFile = path.join(videoDir, 'tmp.mp4');
|
|
30
30
|
|
|
31
|
-
if (
|
|
31
|
+
if (getProperty(options, 'videoParams.keepOriginalVideo', false)) {
|
|
32
32
|
const originalFile = path.join(videoDir, index + '-original.mp4');
|
|
33
33
|
await copyFile(videoPath, originalFile);
|
|
34
34
|
}
|
|
@@ -57,7 +57,7 @@ export async function finetuneVideo(
|
|
|
57
57
|
|
|
58
58
|
if (
|
|
59
59
|
isAndroidConfigured(options) &&
|
|
60
|
-
|
|
60
|
+
getProperty(options, 'videoParams.convert', _convert)
|
|
61
61
|
) {
|
|
62
62
|
const temporaryFile2 = path.join(videoDir, 'tmp-60fps.mp4');
|
|
63
63
|
await convert(temporaryFile, temporaryFile2, 60);
|
|
@@ -2,8 +2,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
|
-
import get from 'lodash.get';
|
|
6
5
|
import { readFile, removeFile, copyFile } from '../../../support/fileUtil.js';
|
|
6
|
+
import { getProperty } from '../../../support/util.js';
|
|
7
|
+
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const log = getLogger('browsertime.video');
|
|
9
10
|
|
|
@@ -48,15 +49,23 @@ export async function run(
|
|
|
48
49
|
visitedPageNumber,
|
|
49
50
|
options
|
|
50
51
|
) {
|
|
51
|
-
const filmstripQuality =
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const filmstripQuality = getProperty(
|
|
53
|
+
options,
|
|
54
|
+
'videoParams.filmstripQuality',
|
|
55
|
+
75
|
|
56
|
+
);
|
|
57
|
+
const createFilmstrip = getProperty(
|
|
58
|
+
options,
|
|
59
|
+
'videoParams.createFilmstrip',
|
|
60
|
+
true
|
|
61
|
+
);
|
|
62
|
+
const fullSizeFilmstrip = getProperty(
|
|
54
63
|
options,
|
|
55
64
|
'videoParams.filmstripFullSize',
|
|
56
65
|
false
|
|
57
66
|
);
|
|
58
67
|
|
|
59
|
-
const thumbsize =
|
|
68
|
+
const thumbsize = getProperty(options, 'videoParams.thumbsize', 400);
|
|
60
69
|
|
|
61
70
|
const scriptArguments = [
|
|
62
71
|
'--video',
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { promisify } from 'node:util';
|
|
2
2
|
import { unlink as _unlink } from 'node:fs';
|
|
3
|
-
import get from 'lodash.get';
|
|
4
3
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
4
|
import { framerate } from '../../defaults.js';
|
|
6
5
|
import { Android } from '../../../android/index.js';
|
|
6
|
+
import { getProperty } from '../../../support/util.js';
|
|
7
|
+
|
|
7
8
|
const unlink = promisify(_unlink);
|
|
8
9
|
const log = getLogger('browsertime.video');
|
|
9
10
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
10
11
|
|
|
11
12
|
export class AndroidRecorder {
|
|
12
13
|
constructor(options) {
|
|
13
|
-
this.waitTime =
|
|
14
|
-
|
|
14
|
+
this.waitTime = getProperty(
|
|
15
|
+
options,
|
|
16
|
+
'videoParams.androidVideoWaitTime',
|
|
17
|
+
5000
|
|
18
|
+
);
|
|
19
|
+
this.framerate = getProperty(options, 'videoParams.framerate', framerate);
|
|
15
20
|
this.options = options;
|
|
16
21
|
}
|
|
17
22
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { promisify } from 'node:util';
|
|
2
2
|
import { unlink as _unlink, rename as _rename } from 'node:fs';
|
|
3
3
|
import { getLogger } from '@sitespeed.io/log';
|
|
4
|
-
import get from 'lodash.get';
|
|
5
4
|
import { start as _start, stop as _stop } from './ffmpegRecorder.js';
|
|
6
5
|
import { convert } from './convert.js';
|
|
7
6
|
import {
|
|
@@ -12,20 +11,22 @@ import {
|
|
|
12
11
|
threads as _threads
|
|
13
12
|
} from '../../defaults.js';
|
|
14
13
|
import { getViewPort } from '../../../support/getViewPort.js';
|
|
14
|
+
import { getProperty } from '../../../support/util.js';
|
|
15
|
+
|
|
15
16
|
const unlink = promisify(_unlink);
|
|
16
17
|
const rename = promisify(_rename);
|
|
17
18
|
const log = getLogger('browsertime.video');
|
|
18
19
|
|
|
19
20
|
export class DesktopRecorder {
|
|
20
21
|
constructor(options) {
|
|
21
|
-
this.display =
|
|
22
|
-
this.framerate =
|
|
23
|
-
this.nice =
|
|
24
|
-
this.crf =
|
|
25
|
-
this.convert =
|
|
26
|
-
this.threads =
|
|
22
|
+
this.display = getProperty(options, 'xvfbParams.display', xvfbDisplay);
|
|
23
|
+
this.framerate = getProperty(options, 'videoParams.framerate', _framerate);
|
|
24
|
+
this.nice = getProperty(options, 'videoParams.nice', 0);
|
|
25
|
+
this.crf = getProperty(options, 'videoParams.crf', _crf);
|
|
26
|
+
this.convert = getProperty(options, 'videoParams.convert', _convert);
|
|
27
|
+
this.threads = getProperty(options, 'videoParams.threads', _threads);
|
|
27
28
|
this.viewPort = getViewPort(options);
|
|
28
|
-
this.taskset =
|
|
29
|
+
this.taskset = getProperty(options, 'videoParams.taskset');
|
|
29
30
|
this.origin = '0,0';
|
|
30
31
|
this.offset = { x: 0, y: 0 };
|
|
31
32
|
this.options = options;
|
package/lib/video/video.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import get from 'lodash.get';
|
|
3
2
|
import { getRecorder } from './screenRecording/recorder.js';
|
|
4
3
|
import { getVideoMetrics } from './postprocessing/visualmetrics/getVideoMetrics.js';
|
|
5
4
|
import { finetuneVideo } from './postprocessing/finetune/index.js';
|
|
6
5
|
import { rename, removeFile, removeDirAndFiles } from '../support/fileUtil.js';
|
|
7
6
|
import { pathToFolder } from '../support/pathToFolder.js';
|
|
8
|
-
|
|
7
|
+
import { getProperty } from '../support/util.js';
|
|
9
8
|
/**
|
|
10
9
|
* Create a new Video that handles everything with the video
|
|
11
10
|
* @class
|
|
@@ -74,7 +73,7 @@ export class Video {
|
|
|
74
73
|
|
|
75
74
|
async cleanup() {
|
|
76
75
|
// Keep or remove the original file
|
|
77
|
-
if (
|
|
76
|
+
if (getProperty(this.options, 'videoParams.keepOriginalVideo', false)) {
|
|
78
77
|
const originalFile = path.join(
|
|
79
78
|
this.videoDir,
|
|
80
79
|
this.index + '-original.mp4'
|