browsertime 16.7.0 → 16.9.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 -1
- package/bin/browsertime.js +3 -0
- package/browsertime/visualmetrics-portable.py +49 -865
- package/lib/chrome/longTaskMetrics.js +15 -0
- package/lib/chrome/webdriver/setupChromiumOptions.js +4 -0
- package/lib/core/engine/command/debug.js +48 -0
- package/lib/core/engine/command/wait.js +23 -0
- package/lib/core/engine/iteration.js +19 -3
- package/lib/core/seleniumRunner.js +27 -17
- package/lib/firefox/webdriver/builder.js +10 -2
- package/lib/firefox/webdriver/firefox.js +14 -13
- package/lib/support/cli.js +27 -1
- package/lib/support/errors.js +9 -1
- package/lib/video/postprocessing/visualmetrics/visualMetrics.js +0 -4
- package/package.json +3 -3
|
@@ -5,6 +5,7 @@ const get = require('lodash.get');
|
|
|
5
5
|
module.exports = function (result, options) {
|
|
6
6
|
let totalDurationFirstPaint = 0;
|
|
7
7
|
let totalDurationFirstContentFulPaint = 0;
|
|
8
|
+
let totalDurationLargestContentFulPaint = 0;
|
|
8
9
|
let totalDurationAfterLoadEventEnd = 0;
|
|
9
10
|
let totalDuration = 0;
|
|
10
11
|
|
|
@@ -22,10 +23,16 @@ module.exports = function (result, options) {
|
|
|
22
23
|
'browserScripts.timings.paintTiming.first-contentful-paint'
|
|
23
24
|
);
|
|
24
25
|
|
|
26
|
+
const largestContentfulPaint = get(
|
|
27
|
+
result,
|
|
28
|
+
'browserScripts.timings.largestContentfulPaint.renderTime'
|
|
29
|
+
); // result.browserScripts.timings.largestContentfulPaint.loadTime;
|
|
30
|
+
|
|
25
31
|
const loadEventEnd = get(result, 'browserScripts.timings.loadEventEnd');
|
|
26
32
|
|
|
27
33
|
let longTasksBeforeFirstPaint = 0;
|
|
28
34
|
let longTasksBeforeFirstContentfulPaint = 0;
|
|
35
|
+
let longTasksBeforeLargestContentfulPaint = 0;
|
|
29
36
|
let longTasksAfterLoadEventEnd = 0;
|
|
30
37
|
let lastLongTask = 0;
|
|
31
38
|
|
|
@@ -42,6 +49,10 @@ module.exports = function (result, options) {
|
|
|
42
49
|
longTasksBeforeFirstContentfulPaint++;
|
|
43
50
|
totalDurationFirstContentFulPaint += longTask.duration;
|
|
44
51
|
}
|
|
52
|
+
if (largestContentfulPaint && longTask.startTime < largestContentfulPaint) {
|
|
53
|
+
longTasksBeforeLargestContentfulPaint++;
|
|
54
|
+
totalDurationLargestContentFulPaint += longTask.duration;
|
|
55
|
+
}
|
|
45
56
|
if (
|
|
46
57
|
firstContentfulPaint &&
|
|
47
58
|
longTask.startTime > firstContentfulPaint &&
|
|
@@ -74,6 +85,10 @@ module.exports = function (result, options) {
|
|
|
74
85
|
tasks: longTasksBeforeFirstContentfulPaint,
|
|
75
86
|
totalDuration: totalDurationFirstContentFulPaint
|
|
76
87
|
},
|
|
88
|
+
beforeLargestContentfulPaint: {
|
|
89
|
+
tasks: longTasksBeforeLargestContentfulPaint,
|
|
90
|
+
totalDuration: totalDurationLargestContentFulPaint
|
|
91
|
+
},
|
|
77
92
|
afterLoadEventEnd: {
|
|
78
93
|
tasks: longTasksAfterLoadEventEnd,
|
|
79
94
|
totalDuration: totalDurationAfterLoadEventEnd
|
|
@@ -99,6 +99,10 @@ module.exports = function (seleniumOptions, browserOptions, options, baseDir) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
if (options.debug) {
|
|
103
|
+
seleniumOptions.addArguments('--auto-open-devtools-for-tabs');
|
|
104
|
+
}
|
|
105
|
+
|
|
102
106
|
const perfLogConf = { enableNetwork: true, enablePage: true };
|
|
103
107
|
seleniumOptions.setPerfLoggingPrefs(perfLogConf);
|
|
104
108
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('intel').getLogger('browsertime.command.debug');
|
|
4
|
+
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
5
|
+
|
|
6
|
+
class Debug {
|
|
7
|
+
constructor(browser, options) {
|
|
8
|
+
this.browser = browser;
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add a breakpoint to script. The browser will wait at the breakpoint for user input.
|
|
14
|
+
* @returns {Promise} Promise object that is fulfilled when the user move on from the breakpoint.
|
|
15
|
+
*/
|
|
16
|
+
async breakpoint(name) {
|
|
17
|
+
if (this.options.debug) {
|
|
18
|
+
const logMessage = `Pausing for breakpoint ${
|
|
19
|
+
name || ''
|
|
20
|
+
}, set browsertime.pause = false; to continue`;
|
|
21
|
+
|
|
22
|
+
await this.browser.runScript(
|
|
23
|
+
`console.log('${logMessage}');`,
|
|
24
|
+
'CONSOLE_LOG'
|
|
25
|
+
);
|
|
26
|
+
log.info(logMessage);
|
|
27
|
+
let debug = true;
|
|
28
|
+
await this.browser.runScript(
|
|
29
|
+
'window.browsertime = {}; window.browsertime.pause = true',
|
|
30
|
+
'PAUSE_SCRIPT'
|
|
31
|
+
);
|
|
32
|
+
while (debug === true) {
|
|
33
|
+
debug = await this.browser.runScript(
|
|
34
|
+
'return window.browsertime.pause',
|
|
35
|
+
'GET_PAUSE_STATUS'
|
|
36
|
+
);
|
|
37
|
+
await delay(1000);
|
|
38
|
+
}
|
|
39
|
+
log.info('Exit breakpoint and continue.');
|
|
40
|
+
return this.browser.runScript(
|
|
41
|
+
'window.browsertime.pause = false',
|
|
42
|
+
'SET_PAUSE_STATUS'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Debug;
|
|
@@ -101,5 +101,28 @@ class Wait {
|
|
|
101
101
|
async byPageToComplete() {
|
|
102
102
|
return this.browser.extraWait(this.pageCompleteCheck);
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Wait for an condition that will eventually return a truthy-value for maxTime.
|
|
107
|
+
* @param {string} jsExpression The js code condition to wait for
|
|
108
|
+
* @param {number} maxTime Max time to wait in ms
|
|
109
|
+
* @returns {Promise} Promise object represents when the expression becomes truthy or the time times out
|
|
110
|
+
* @throws Will throw an error if the condition returned false
|
|
111
|
+
*/
|
|
112
|
+
async byCondition(jsExpression, maxTime) {
|
|
113
|
+
const driver = this.browser.getDriver();
|
|
114
|
+
const time = maxTime || 6000;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const script = `return ${jsExpression}`;
|
|
118
|
+
await driver.wait(() => {
|
|
119
|
+
return driver.executeScript(script);
|
|
120
|
+
}, time);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
log.error('Condition was not met', jsExpression, time);
|
|
123
|
+
log.verbose(e);
|
|
124
|
+
throw Error(`Condition ${jsExpression} was not met in ${time} ms`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
104
127
|
}
|
|
105
128
|
module.exports = Wait;
|
|
@@ -22,6 +22,7 @@ const Cache = require('./command/cache.js');
|
|
|
22
22
|
const Meta = require('./command/meta.js');
|
|
23
23
|
const StopWatch = require('./command/stopWatch.js');
|
|
24
24
|
const Select = require('./command/select.js');
|
|
25
|
+
const Debug = require('./command/debug.js');
|
|
25
26
|
const AndroidCommand = require('./command/android.js');
|
|
26
27
|
const ChromeDevToolsProtocol = require('./command/chromeDevToolsProtocol.js');
|
|
27
28
|
const { addConnectivity, removeConnectivity } = require('../../connectivity');
|
|
@@ -148,7 +149,7 @@ class Iteration {
|
|
|
148
149
|
|
|
149
150
|
const cdp = new ChromeDevToolsProtocol(engineDelegate, options.browser);
|
|
150
151
|
const android = new Android(options);
|
|
151
|
-
|
|
152
|
+
const debug = new Debug(browser, options);
|
|
152
153
|
const commands = {
|
|
153
154
|
click: new Click(browser, this.pageCompleteCheck),
|
|
154
155
|
scroll: new Scroll(browser, options),
|
|
@@ -172,6 +173,7 @@ class Iteration {
|
|
|
172
173
|
screenshot: new Screenshot(screenshotManager, browser, index),
|
|
173
174
|
cdp,
|
|
174
175
|
android: new AndroidCommand(options),
|
|
176
|
+
debug: debug,
|
|
175
177
|
mouse: {
|
|
176
178
|
moveTo: new MouseMove(browser),
|
|
177
179
|
contextClick: new ContextClick(browser),
|
|
@@ -244,7 +246,17 @@ class Iteration {
|
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
await engineDelegate.beforeBrowserStop(browser, index, result);
|
|
247
|
-
|
|
249
|
+
|
|
250
|
+
// if we are in debig mode we wait on a continue command
|
|
251
|
+
if (options.debug) {
|
|
252
|
+
await debug.breakpoint('End-of-iteration');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (options.browser === 'firefox' && options.debug) {
|
|
256
|
+
log.info('Firefox is kept open in debug mode');
|
|
257
|
+
} else {
|
|
258
|
+
await browser.stop();
|
|
259
|
+
}
|
|
248
260
|
// Give the browsers some time to stop
|
|
249
261
|
await delay(2000);
|
|
250
262
|
await engineDelegate.afterBrowserStopped();
|
|
@@ -329,7 +341,11 @@ class Iteration {
|
|
|
329
341
|
if (options.connectivity.variance) {
|
|
330
342
|
await removeConnectivity(options);
|
|
331
343
|
}
|
|
332
|
-
|
|
344
|
+
if (options.browser === 'firefox' && options.debug) {
|
|
345
|
+
log.info('Firefox is kept open in debug mode');
|
|
346
|
+
} else {
|
|
347
|
+
await browser.stop();
|
|
348
|
+
}
|
|
333
349
|
} catch (e) {
|
|
334
350
|
// Most cases the browser been stopped already
|
|
335
351
|
}
|
|
@@ -5,7 +5,11 @@ const merge = require('lodash.merge');
|
|
|
5
5
|
const get = require('lodash.get');
|
|
6
6
|
const { Condition } = require('selenium-webdriver/lib/webdriver');
|
|
7
7
|
const { isAndroidConfigured } = require('../android');
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
UrlLoadError,
|
|
10
|
+
BrowserError,
|
|
11
|
+
TimeoutError
|
|
12
|
+
} = require('../support/errors');
|
|
9
13
|
const builder = require('./webdriver');
|
|
10
14
|
const getViewPort = require('../support/getViewPort');
|
|
11
15
|
const defaultPageCompleteCheck = require('./pageCompleteChecks/defaultPageCompleteCheck');
|
|
@@ -18,7 +22,7 @@ const defaults = {
|
|
|
18
22
|
pageLoad: 300000,
|
|
19
23
|
script: 120000,
|
|
20
24
|
logs: 90000,
|
|
21
|
-
pageCompleteCheck:
|
|
25
|
+
pageCompleteCheck: 60000
|
|
22
26
|
},
|
|
23
27
|
index: 0
|
|
24
28
|
};
|
|
@@ -36,7 +40,7 @@ async function timeout(promise, ms, errorMessage) {
|
|
|
36
40
|
|
|
37
41
|
return Promise.race([
|
|
38
42
|
new Promise((resolve, reject) => {
|
|
39
|
-
timer = setTimeout(reject, ms, new
|
|
43
|
+
timer = setTimeout(reject, ms, new TimeoutError(errorMessage));
|
|
40
44
|
return timer;
|
|
41
45
|
}),
|
|
42
46
|
promise.then(value => {
|
|
@@ -182,20 +186,26 @@ class SeleniumRunner {
|
|
|
182
186
|
`Running page complete check ${pageCompleteCheck} took too long`
|
|
183
187
|
);
|
|
184
188
|
} catch (e) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
189
|
+
if (e instanceof TimeoutError) {
|
|
190
|
+
log.info(
|
|
191
|
+
`The page did not finishied loading in ${pageCompleteCheckTimeout} ms. You can adjust the timeout by setting the --maxLoadTime option (in ms).`
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
log.error(
|
|
195
|
+
`Failed waiting on page ${
|
|
196
|
+
url ? url : ''
|
|
197
|
+
} to finished loading, timed out after ${pageCompleteCheckTimeout} ms`,
|
|
198
|
+
e
|
|
199
|
+
);
|
|
200
|
+
throw new UrlLoadError(
|
|
201
|
+
`Failed waiting on page ${
|
|
202
|
+
url ? url : ''
|
|
203
|
+
} to finished loading, timed out after ${pageCompleteCheckTimeout} ms `,
|
|
204
|
+
{
|
|
205
|
+
cause: e
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
}
|
|
199
209
|
}
|
|
200
210
|
}
|
|
201
211
|
|
|
@@ -103,6 +103,12 @@ module.exports.configureBuilder = function (builder, baseDir, options) {
|
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
if (options.debug) {
|
|
107
|
+
ffOptions.addArguments('--devtools');
|
|
108
|
+
// Mozilla use detach to make sure you can inspect the browser
|
|
109
|
+
ffOptions.setPreference('detach', true);
|
|
110
|
+
}
|
|
111
|
+
|
|
106
112
|
if (!options.skipHar) {
|
|
107
113
|
if (options.android) {
|
|
108
114
|
log.info('Skip install HAR trigger on Android');
|
|
@@ -110,8 +116,10 @@ module.exports.configureBuilder = function (builder, baseDir, options) {
|
|
|
110
116
|
// Hack for opening the toolbar
|
|
111
117
|
// In Firefox 61 we need to have devtools open but do not need to choose netmonitor
|
|
112
118
|
// ffOptions.setPreference('devtools.toolbox.selectedTool', 'netmonitor');
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
if (!options.debug) {
|
|
120
|
+
ffOptions.setPreference('devtools.toolbox.footer.height', 0);
|
|
121
|
+
ffOptions.addArguments('-devtools');
|
|
122
|
+
}
|
|
115
123
|
|
|
116
124
|
ffOptions.addExtensions(
|
|
117
125
|
path.resolve(
|
|
@@ -104,19 +104,6 @@ class Firefox {
|
|
|
104
104
|
*/
|
|
105
105
|
async afterPageCompleteCheck(runner, index, url, alias) {
|
|
106
106
|
const result = { url, alias };
|
|
107
|
-
if (this.firefoxConfig.collectMozLog) {
|
|
108
|
-
const files = await fileUtil.findFiles(this.baseDir, 'moz_log.txt');
|
|
109
|
-
for (const file of files) {
|
|
110
|
-
await rename(
|
|
111
|
-
`${this.baseDir}/${file}`,
|
|
112
|
-
path.join(
|
|
113
|
-
this.baseDir,
|
|
114
|
-
pathToFolder(result.url, this.options),
|
|
115
|
-
`${file}-${index}.txt`
|
|
116
|
-
)
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
107
|
|
|
121
108
|
if (this.android && this.options.androidPower) {
|
|
122
109
|
result.power = await this.android.measurePowerUsage(
|
|
@@ -192,6 +179,20 @@ class Firefox {
|
|
|
192
179
|
|
|
193
180
|
async postWork(index, results) {
|
|
194
181
|
for (let i = 0; i < results.length; i++) {
|
|
182
|
+
if (this.firefoxConfig.collectMozLog) {
|
|
183
|
+
const files = await fileUtil.findFiles(this.baseDir, 'moz_log.txt');
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
await rename(
|
|
186
|
+
`${this.baseDir}/${file}`,
|
|
187
|
+
path.join(
|
|
188
|
+
this.baseDir,
|
|
189
|
+
pathToFolder(results[i].url, this.options),
|
|
190
|
+
`${file}-${index}.txt`
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
195
196
|
if (this.firefoxConfig.geckoProfiler && this.options.visualMetrics) {
|
|
196
197
|
const profileFilename = `geckoProfile-${index}.json.gz`;
|
|
197
198
|
const profileSubdir = pathToFolder(results[i].url, this.options);
|
package/lib/support/cli.js
CHANGED
|
@@ -75,6 +75,18 @@ function validateInput(argv) {
|
|
|
75
75
|
return 'You need to specify the --safari.deviceUDID when you run the simulator.';
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
if (argv.debug && argv.browser === 'safari') {
|
|
79
|
+
return 'Debug mode do not work in Safari. Please try with Firefox/Chrome or Edge';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (argv.debug && argv.android) {
|
|
83
|
+
return 'Debug mode do not work on Android. Please run debug mode on Desktop.';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (argv.debug && argv.docker) {
|
|
87
|
+
return 'There is no benefit running debug mode inside a Docker container.';
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
if (argv.tcpdump) {
|
|
79
91
|
if (!hasbin.all.sync(['tcpdump'])) {
|
|
80
92
|
return 'You need to have tcpdump in your path to be able to record a tcpdump.';
|
|
@@ -150,7 +162,8 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|
|
150
162
|
group: 'timeouts'
|
|
151
163
|
})
|
|
152
164
|
.option('timeouts.pageCompleteCheck', {
|
|
153
|
-
|
|
165
|
+
alias: 'maxLoadTime',
|
|
166
|
+
default: 120000,
|
|
154
167
|
type: 'number',
|
|
155
168
|
describe:
|
|
156
169
|
'Timeout when waiting for page to complete loading, in milliseconds',
|
|
@@ -1159,6 +1172,12 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|
|
1159
1172
|
describe:
|
|
1160
1173
|
'The wait time before you start the real testing after your pre-cache request.'
|
|
1161
1174
|
})
|
|
1175
|
+
.option('debug', {
|
|
1176
|
+
type: 'boolean',
|
|
1177
|
+
default: false,
|
|
1178
|
+
describe: 'Run Browsertime in debug mode.',
|
|
1179
|
+
group: 'debug'
|
|
1180
|
+
})
|
|
1162
1181
|
|
|
1163
1182
|
.count('verbose')
|
|
1164
1183
|
.config(config)
|
|
@@ -1253,6 +1272,13 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|
|
1253
1272
|
set(argv, 'visualMetrics', get(argv, 'visualMetrics', true));
|
|
1254
1273
|
}
|
|
1255
1274
|
|
|
1275
|
+
// Set the timeouts to a maximum while debugging
|
|
1276
|
+
if (argv.debug) {
|
|
1277
|
+
set(argv, 'timeouts.pageload', 2147483647);
|
|
1278
|
+
set(argv, 'timeouts.script', 2147483647);
|
|
1279
|
+
set(argv, 'timeouts.pageCompleteCheck', 2147483647);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1256
1282
|
return {
|
|
1257
1283
|
urls: argv._,
|
|
1258
1284
|
options: argv
|
package/lib/support/errors.js
CHANGED
|
@@ -21,8 +21,16 @@ class UrlLoadError extends BrowsertimeError {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
class TimeoutError extends BrowsertimeError {
|
|
25
|
+
constructor(message, url, extra) {
|
|
26
|
+
super(message, extra);
|
|
27
|
+
this.url = url;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
module.exports = {
|
|
25
32
|
BrowsertimeError,
|
|
26
33
|
BrowserError,
|
|
27
|
-
UrlLoadError
|
|
34
|
+
UrlLoadError,
|
|
35
|
+
TimeoutError
|
|
28
36
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"description": "Browsertime",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.9.0",
|
|
4
4
|
"bin": "./bin/browsertime.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@cypress/xvfb": "1.2.4",
|
|
7
7
|
"@devicefarmer/adbkit": "2.11.3",
|
|
8
|
-
"@sitespeed.io/chromedriver": "
|
|
8
|
+
"@sitespeed.io/chromedriver": "102.0.5005-27",
|
|
9
9
|
"@sitespeed.io/edgedriver": "101.0.1210-32",
|
|
10
10
|
"@sitespeed.io/geckodriver": "0.31.0",
|
|
11
11
|
"@sitespeed.io/throttle": "3.1.1",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"lodash.merge": "4.6.2",
|
|
27
27
|
"lodash.pick": "4.4.0",
|
|
28
28
|
"lodash.set": "4.3.2",
|
|
29
|
-
"selenium-webdriver": "4.
|
|
29
|
+
"selenium-webdriver": "4.2.0",
|
|
30
30
|
"yargs": "17.4.1"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|