browsertime 16.5.0 → 16.8.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 +18 -1
- package/bin/browsertime.js +3 -0
- package/browserscripts/pageinfo/interactionToNextPaintInfo.js +117 -0
- package/browserscripts/timings/interactionToNextPaint.js +116 -0
- package/lib/chrome/longTaskMetrics.js +5 -0
- package/lib/chrome/webdriver/chromium.js +9 -0
- package/lib/chrome/webdriver/setupChromiumOptions.js +4 -0
- package/lib/core/engine/command/debug.js +47 -0
- package/lib/core/engine/iteration.js +19 -3
- package/lib/firefox/webdriver/builder.js +10 -2
- package/lib/support/cli.js +25 -0
- package/lib/support/har/index.js +4 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
-
## 16.
|
|
3
|
+
## 16.8.0 - 2022-06-06
|
|
4
|
+
### Added
|
|
5
|
+
* Add `--debug` mode. Debug mode will run your tests and open devtools in Chrome/Edge/Firefox on desktop and will stop after each iteration so you can inspect the page. You can add your own breakpoint in your script with the `breakpoint(name)` command. To continue after your breakpoint, add the following code in the developer console in your browser: `window.browsertime.pause=false;` [#1798](https://github.com/sitespeedio/browsertime/pull/1798).
|
|
6
|
+
* Use Selenium WebDriver 4.2.0 [#1801](https://github.com/sitespeedio/browsertime/pull/1801).
|
|
7
|
+
* Updated to Firefox 101, Edge 102 in the Docker container.
|
|
8
|
+
|
|
9
|
+
### Tech
|
|
10
|
+
* Added tests using NodeJS 18 [#1772](https://github.com/sitespeedio/browsertime/pull/1772).
|
|
11
|
+
## 16.7.0 - 2022-05-20
|
|
12
|
+
### Added
|
|
13
|
+
* Include last CPU long task in the HAR file so we can show when it happens [#1793](https://github.com/sitespeedio/browsertime/pull/1793).
|
|
14
|
+
* Inlclude TTFB and INP in the Google Web Vital namespace [#1792](https://github.com/sitespeedio/browsertime/pull/1792).
|
|
15
|
+
## 16.6.0 - 2022-05-20
|
|
16
|
+
### Added
|
|
17
|
+
* Implemented experimental Interaction to next paint that's useful if you test user journeys [#1791](https://github.com/sitespeedio/browsertime/pull/1791).
|
|
18
|
+
* Track when the last CPU long task happen as explained by Andy Davies of the webperf Slack channel [#1789](https://github.com/sitespeedio/browsertime/pull/1789).
|
|
19
|
+
|
|
20
|
+
## 16.5.0 - 2022-05-11
|
|
4
21
|
### Added
|
|
5
22
|
* Make it possible to configure the max size in pixels for the filmstrip screenshots using `--videoParams.thumbsize` [#1787](https://github.com/sitespeedio/browsertime/pull/1787).
|
|
6
23
|
|
package/bin/browsertime.js
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
// This is an updated version of
|
|
3
|
+
// https://github.com/GoogleChrome/web-vitals/blob/next/src/onINP.ts
|
|
4
|
+
|
|
5
|
+
const supported = PerformanceObserver.supportedEntryTypes;
|
|
6
|
+
if (!supported || supported.indexOf('event') === -1) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const observer = new PerformanceObserver(list => {});
|
|
11
|
+
// Event Timing entries have their durations rounded to the nearest 8ms,
|
|
12
|
+
// so a duration of 40ms would be any event that spans 2.5 or more frames
|
|
13
|
+
// at 60Hz. This threshold is chosen to strike a balance between usefulness
|
|
14
|
+
// and performance. Running this callback for any interaction that spans
|
|
15
|
+
// just one or two frames is likely not worth the insight that could be
|
|
16
|
+
// gained.
|
|
17
|
+
observer.observe({type: 'event', buffered: true, durationThreshold: 40});
|
|
18
|
+
const entries = observer.takeRecords();
|
|
19
|
+
|
|
20
|
+
// To prevent unnecessary memory usage on pages with lots of interactions,
|
|
21
|
+
// store at most 10 of the longest interactions to consider as INP candidates.
|
|
22
|
+
const MAX_INTERACTIONS_TO_CONSIDER = 10;
|
|
23
|
+
|
|
24
|
+
// A list of longest interactions on the page (by latency) sorted so the
|
|
25
|
+
// longest one is first. The list is as most MAX_INTERACTIONS_TO_CONSIDER long.
|
|
26
|
+
let longestInteractionList = [];
|
|
27
|
+
|
|
28
|
+
// A mapping of longest interactions by their interaction ID.
|
|
29
|
+
// This is used for faster lookup.
|
|
30
|
+
const longestInteractionMap = {};
|
|
31
|
+
|
|
32
|
+
const getInteractionCountForNavigation = () => {
|
|
33
|
+
// I guess the interactionCount is coming in Chrome later on
|
|
34
|
+
if (performance.interactionCount) {
|
|
35
|
+
return performance.interactionCount;
|
|
36
|
+
} else {
|
|
37
|
+
const observerForAll = new PerformanceObserver(list => {});
|
|
38
|
+
observerForAll.observe({
|
|
39
|
+
type: 'event',
|
|
40
|
+
buffered: true,
|
|
41
|
+
durationThreshold: 0
|
|
42
|
+
});
|
|
43
|
+
const allEntries = observerForAll.takeRecords();
|
|
44
|
+
let interactionCountEstimate = 0;
|
|
45
|
+
let minKnownInteractionId = Infinity;
|
|
46
|
+
let maxKnownInteractionId = 0;
|
|
47
|
+
|
|
48
|
+
for (let e of allEntries) {
|
|
49
|
+
if (e.interactionId) {
|
|
50
|
+
minKnownInteractionId = Math.min(
|
|
51
|
+
minKnownInteractionId,
|
|
52
|
+
e.interactionId
|
|
53
|
+
);
|
|
54
|
+
maxKnownInteractionId = Math.max(
|
|
55
|
+
maxKnownInteractionId,
|
|
56
|
+
e.interactionId
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
interactionCountEstimate = maxKnownInteractionId
|
|
60
|
+
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
|
|
61
|
+
: 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return interactionCountEstimate;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const estimateP98LongestInteraction = () => {
|
|
69
|
+
const candidateInteractionIndex = Math.min(
|
|
70
|
+
longestInteractionList.length - 1,
|
|
71
|
+
Math.floor(getInteractionCountForNavigation() / 50)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return longestInteractionList[candidateInteractionIndex];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (entries.length > 0) {
|
|
78
|
+
for (let entry of entries) {
|
|
79
|
+
const minLongestInteraction =
|
|
80
|
+
longestInteractionList[longestInteractionList.length - 1];
|
|
81
|
+
const existingInteraction = longestInteractionMap[entry.interactionId];
|
|
82
|
+
if (
|
|
83
|
+
existingInteraction ||
|
|
84
|
+
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
|
|
85
|
+
entry.duration > minLongestInteraction.latency
|
|
86
|
+
) {
|
|
87
|
+
// If the interaction already exists, update it. Otherwise create one.
|
|
88
|
+
if (existingInteraction) {
|
|
89
|
+
existingInteraction.entries.push(entry);
|
|
90
|
+
existingInteraction.latency = Math.max(
|
|
91
|
+
existingInteraction.latency,
|
|
92
|
+
entry.duration
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
const interaction = {
|
|
96
|
+
id: entry.interactionId,
|
|
97
|
+
latency: entry.duration,
|
|
98
|
+
entries: [entry]
|
|
99
|
+
};
|
|
100
|
+
longestInteractionMap[interaction.id] = interaction;
|
|
101
|
+
longestInteractionList.push(interaction);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sort the entries by latency (descending) and keep only the top ten.
|
|
105
|
+
longestInteractionList.sort((a, b) => b.latency - a.latency);
|
|
106
|
+
longestInteractionList
|
|
107
|
+
.splice(MAX_INTERACTIONS_TO_CONSIDER)
|
|
108
|
+
.forEach(i => {
|
|
109
|
+
delete longestInteractionMap[i.id];
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return estimateP98LongestInteraction();
|
|
114
|
+
} else {
|
|
115
|
+
// nothing to report
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
// This is an updated version of
|
|
3
|
+
// https://github.com/GoogleChrome/web-vitals/blob/next/src/onINP.ts
|
|
4
|
+
|
|
5
|
+
const supported = PerformanceObserver.supportedEntryTypes;
|
|
6
|
+
if (!supported || supported.indexOf('event') === -1) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// To prevent unnecessary memory usage on pages with lots of interactions,
|
|
11
|
+
// store at most 10 of the longest interactions to consider as INP candidates.
|
|
12
|
+
const MAX_INTERACTIONS_TO_CONSIDER = 10;
|
|
13
|
+
|
|
14
|
+
// A list of longest interactions on the page (by latency) sorted so the
|
|
15
|
+
// longest one is first. The list is as most MAX_INTERACTIONS_TO_CONSIDER long.
|
|
16
|
+
let longestInteractionList = [];
|
|
17
|
+
|
|
18
|
+
// A mapping of longest interactions by their interaction ID.
|
|
19
|
+
// This is used for faster lookup.
|
|
20
|
+
const longestInteractionMap = {};
|
|
21
|
+
|
|
22
|
+
const getInteractionCountForNavigation = () => {
|
|
23
|
+
// I guess the interactionCount is coming in Chrome later on
|
|
24
|
+
if (performance.interactionCount) {
|
|
25
|
+
return performance.interactionCount;
|
|
26
|
+
} else {
|
|
27
|
+
const observerForAll = new PerformanceObserver(list => {});
|
|
28
|
+
observerForAll.observe({
|
|
29
|
+
type: 'event',
|
|
30
|
+
buffered: true,
|
|
31
|
+
durationThreshold: 0
|
|
32
|
+
});
|
|
33
|
+
const allEntries = observerForAll.takeRecords();
|
|
34
|
+
let interactionCountEstimate = 0;
|
|
35
|
+
let minKnownInteractionId = Infinity;
|
|
36
|
+
let maxKnownInteractionId = 0;
|
|
37
|
+
|
|
38
|
+
for (let e of allEntries) {
|
|
39
|
+
if (e.interactionId) {
|
|
40
|
+
minKnownInteractionId = Math.min(
|
|
41
|
+
minKnownInteractionId,
|
|
42
|
+
e.interactionId
|
|
43
|
+
);
|
|
44
|
+
maxKnownInteractionId = Math.max(
|
|
45
|
+
maxKnownInteractionId,
|
|
46
|
+
e.interactionId
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
interactionCountEstimate = maxKnownInteractionId
|
|
50
|
+
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
|
|
51
|
+
: 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return interactionCountEstimate;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const estimateP98LongestInteraction = () => {
|
|
59
|
+
const candidateInteractionIndex = Math.min(
|
|
60
|
+
longestInteractionList.length - 1,
|
|
61
|
+
Math.floor(getInteractionCountForNavigation() / 50)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return longestInteractionList[candidateInteractionIndex];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const observer = new PerformanceObserver(list => {});
|
|
68
|
+
// Event Timing entries have their durations rounded to the nearest 8ms,
|
|
69
|
+
// so a duration of 40ms would be any event that spans 2.5 or more frames
|
|
70
|
+
// at 60Hz. This threshold is chosen to strike a balance between usefulness
|
|
71
|
+
// and performance. Running this callback for any interaction that spans
|
|
72
|
+
// just one or two frames is likely not worth the insight that could be
|
|
73
|
+
// gained.
|
|
74
|
+
observer.observe({type: 'event', buffered: true, durationThreshold: 40});
|
|
75
|
+
const entries = observer.takeRecords();
|
|
76
|
+
|
|
77
|
+
if (entries.length > 0) {
|
|
78
|
+
for (let entry of entries) {
|
|
79
|
+
const minLongestInteraction =
|
|
80
|
+
longestInteractionList[longestInteractionList.length - 1];
|
|
81
|
+
const existingInteraction = longestInteractionMap[entry.interactionId];
|
|
82
|
+
if (
|
|
83
|
+
existingInteraction ||
|
|
84
|
+
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
|
|
85
|
+
entry.duration > minLongestInteraction.latency
|
|
86
|
+
) {
|
|
87
|
+
// If the interaction already exists, update it. Otherwise create one.
|
|
88
|
+
if (existingInteraction) {
|
|
89
|
+
existingInteraction.latency = Math.max(
|
|
90
|
+
existingInteraction.latency,
|
|
91
|
+
entry.duration
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
const interaction = {
|
|
95
|
+
id: entry.interactionId,
|
|
96
|
+
latency: entry.duration
|
|
97
|
+
};
|
|
98
|
+
longestInteractionMap[interaction.id] = interaction;
|
|
99
|
+
longestInteractionList.push(interaction);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sort the entries by latency (descending) and keep only the top ten.
|
|
103
|
+
longestInteractionList.sort((a, b) => b.latency - a.latency);
|
|
104
|
+
longestInteractionList
|
|
105
|
+
.splice(MAX_INTERACTIONS_TO_CONSIDER)
|
|
106
|
+
.forEach(i => {
|
|
107
|
+
delete longestInteractionMap[i.id];
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const inp = estimateP98LongestInteraction();
|
|
112
|
+
return inp.latency;
|
|
113
|
+
} else {
|
|
114
|
+
// nothing to report
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
@@ -27,9 +27,13 @@ module.exports = function (result, options) {
|
|
|
27
27
|
let longTasksBeforeFirstPaint = 0;
|
|
28
28
|
let longTasksBeforeFirstContentfulPaint = 0;
|
|
29
29
|
let longTasksAfterLoadEventEnd = 0;
|
|
30
|
+
let lastLongTask = 0;
|
|
30
31
|
|
|
31
32
|
for (let longTask of result.browserScripts.pageinfo.longTask) {
|
|
32
33
|
totalDuration += longTask.duration;
|
|
34
|
+
if (longTask.startTime > lastLongTask) {
|
|
35
|
+
lastLongTask = longTask.startTime;
|
|
36
|
+
}
|
|
33
37
|
if (firstPaint && longTask.startTime < firstPaint) {
|
|
34
38
|
longTasksBeforeFirstPaint++;
|
|
35
39
|
totalDurationFirstPaint += longTask.duration;
|
|
@@ -61,6 +65,7 @@ module.exports = function (result, options) {
|
|
|
61
65
|
totalDuration,
|
|
62
66
|
totalBlockingTime: Number(totalBlockingTime.toFixed(0)),
|
|
63
67
|
maxPotentialFid: Number(maxPotentialFid.toFixed(0)),
|
|
68
|
+
lastLongTask: Number(lastLongTask.toFixed(0)),
|
|
64
69
|
beforeFirstPaint: {
|
|
65
70
|
tasks: longTasksBeforeFirstPaint,
|
|
66
71
|
totalDuration: totalDurationFirstPaint
|
|
@@ -320,6 +320,10 @@ class Chromium {
|
|
|
320
320
|
result.browserScripts.pageinfo.cumulativeLayoutShift;
|
|
321
321
|
}
|
|
322
322
|
if (result.browserScripts.timings) {
|
|
323
|
+
if (result.browserScripts.timings.ttfb) {
|
|
324
|
+
result.googleWebVitals.ttfb = result.browserScripts.timings.ttfb;
|
|
325
|
+
}
|
|
326
|
+
|
|
323
327
|
if (result.browserScripts.timings.largestContentfulPaint) {
|
|
324
328
|
result.googleWebVitals.largestContentfulPaint =
|
|
325
329
|
result.browserScripts.timings.largestContentfulPaint.renderTime ||
|
|
@@ -338,6 +342,11 @@ class Chromium {
|
|
|
338
342
|
: 0;
|
|
339
343
|
}
|
|
340
344
|
|
|
345
|
+
if (result.browserScripts.timings.interactionToNextPaint) {
|
|
346
|
+
result.googleWebVitals.interactionToNextPaint =
|
|
347
|
+
result.browserScripts.timings.interactionToNextPaint;
|
|
348
|
+
}
|
|
349
|
+
|
|
341
350
|
// Add LCP to the HAR
|
|
342
351
|
if (
|
|
343
352
|
result.browserScripts.timings.largestContentfulPaint &&
|
|
@@ -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,47 @@
|
|
|
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
|
+
return this.browser.runScript(
|
|
40
|
+
'window.browsertime.pause = false',
|
|
41
|
+
'SET_PAUSE_STATUS'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = Debug;
|
|
@@ -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
|
}
|
|
@@ -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(
|
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.';
|
|
@@ -1159,6 +1171,12 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|
|
1159
1171
|
describe:
|
|
1160
1172
|
'The wait time before you start the real testing after your pre-cache request.'
|
|
1161
1173
|
})
|
|
1174
|
+
.option('debug', {
|
|
1175
|
+
type: 'boolean',
|
|
1176
|
+
default: false,
|
|
1177
|
+
describe: 'Run Browsertime in debug mode.',
|
|
1178
|
+
group: 'debug'
|
|
1179
|
+
})
|
|
1162
1180
|
|
|
1163
1181
|
.count('verbose')
|
|
1164
1182
|
.config(config)
|
|
@@ -1253,6 +1271,13 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|
|
1253
1271
|
set(argv, 'visualMetrics', get(argv, 'visualMetrics', true));
|
|
1254
1272
|
}
|
|
1255
1273
|
|
|
1274
|
+
// Set the timeouts to a maximum while debugging
|
|
1275
|
+
if (argv.debug) {
|
|
1276
|
+
set(argv, 'timeouts.pageload', 2147483647);
|
|
1277
|
+
set(argv, 'timeouts.script', 2147483647);
|
|
1278
|
+
set(argv, 'timeouts.pageCompleteCheck', 2147483647);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1256
1281
|
return {
|
|
1257
1282
|
urls: argv._,
|
|
1258
1283
|
options: argv
|
package/lib/support/har/index.js
CHANGED
|
@@ -91,6 +91,10 @@ function addExtrasToHAR(
|
|
|
91
91
|
harPageTimings._firstPaint = timings.firstPaint;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
if (cpu && cpu.longTasks && cpu.longTasks.lastLongTask) {
|
|
95
|
+
harPageTimings._lastCPULongTask = cpu.longTasks.lastLongTask;
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
if (timings && timings.largestContentfulPaint) {
|
|
95
99
|
harPageTimings._largestContentfulPaint =
|
|
96
100
|
timings.largestContentfulPaint.renderTime;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"description": "Browsertime",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.8.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": {
|