browsertime 17.17.0 → 17.18.1
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 -0
- package/browserscripts/pageinfo/resources.js +17 -4
- package/lib/chrome/networkManager.js +61 -0
- package/lib/chrome/webdriver/chromium.js +6 -0
- package/lib/core/engine/command/geckoProfiler.js +3 -6
- package/lib/core/engine/command/measure.js +10 -2
- package/lib/core/engine/iteration.js +2 -1
- package/lib/core/seleniumRunner.js +111 -90
- package/lib/firefox/networkManager.js +109 -0
- package/lib/firefox/webdriver/builder.js +2 -0
- package/lib/firefox/webdriver/firefox.js +8 -0
- package/lib/safari/webdriver/safari.js +4 -0
- package/lib/support/cli.js +13 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
+
## 17.18.1 - 2022-10-31
|
|
4
|
+
### Fixed
|
|
5
|
+
* WebDriver 4.14.0
|
|
6
|
+
* Chromedriver 119.0.6045-59
|
|
7
|
+
|
|
8
|
+
## 17.18.0 - 2022-10-23
|
|
9
|
+
### Added
|
|
10
|
+
* Updated to Chromedriver 119 [#2003](https://github.com/sitespeedio/browsertime/pull/2003). 119 works with both Chrome 118 and 119 so it fixes [#1197](https://github.com/sitespeedio/browsertime/issues/1997).
|
|
11
|
+
* Add support for network idle method to know when to end a test that uses network logs. Uses Bidi for Firefox and CDP for Chrome to listen on network events to know when to end a test. By default 5 seconds idle network time ends a tests (you could have network responses that hasn't arrived yet) [#1960](https://github.com/sitespeedio/browsertime/pull/1960). Potentially this can help SPA users or users where the page uses iframes. You can try it out by adding `--pageCompleteCheckNetworkIdle` yo your command line. This is still some work in progress but feel free to try ut out.
|
|
12
|
+
* The resources script now collects number of resources served from the browser cashe for browser that supports that through the resource timing API [#1998](https://github.com/sitespeedio/browsertime/pull/1998)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
* Make sure timer always is cleared. There was case of where we do a rase beteween a promise and a timeout where the timeout timer wasn't cleared/removed [#2005](https://github.com/sitespeedio/browsertime/pull/2005).
|
|
16
|
+
* Better way to get the url when you use GeckoProfiler.stop for Firefox, thank you [Nazım Can Altınova](https://github.com/canova) for PR [#1999](https://github.com/sitespeedio/browsertime/pull/1999)
|
|
17
|
+
|
|
3
18
|
## 17.17.0 - 2022-10-11
|
|
4
19
|
### Added
|
|
5
20
|
* Firefox 118, Edge 117 and Chrome/Chromedriver 118 in the Docker container [#1996](https://github.com/sitespeedio/browsertime/pull/1996).
|
|
6
21
|
* Expose trace start/stop for Chrome in scripting (the same way as for Firefox). Thank you [KS](https://github.com/92kns) for [#1988](https://github.com/sitespeedio/browsertime/pull/1988). Documentation is coming when the functionality is rolled out in sitespeed.io.
|
|
7
22
|
|
|
23
|
+
### Updated
|
|
24
|
+
* Updated to Selenium 4.12
|
|
25
|
+
|
|
8
26
|
## 17.16.0 - 2022-09-04
|
|
9
27
|
### Added
|
|
10
28
|
* Firefox 117 and Edge 116 in the Docker container.
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
(function() {
|
|
1
|
+
(function () {
|
|
2
2
|
const resources = window.performance.getEntriesByType('resource');
|
|
3
3
|
|
|
4
4
|
let resourceDuration = 0;
|
|
5
|
+
let servedFromCache = 0;
|
|
6
|
+
let servedFromCacheSupported = false;
|
|
5
7
|
for (let i = 0; i < resources.length; i++) {
|
|
6
8
|
resourceDuration += resources[i].duration;
|
|
9
|
+
if (resources[i].deliveryType !== undefined) {
|
|
10
|
+
servedFromCacheSupported = true;
|
|
11
|
+
if (resources[i].deliveryType === 'cache') {
|
|
12
|
+
servedFromCache++;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
7
15
|
}
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
const info = {
|
|
10
18
|
count: Number(resources.length),
|
|
11
|
-
duration: Number(resourceDuration)
|
|
19
|
+
duration: Number(resourceDuration),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (servedFromCacheSupported === true) {
|
|
23
|
+
info.servedFromCache = Number(servedFromCache);
|
|
12
24
|
}
|
|
13
|
-
})();
|
|
14
25
|
|
|
26
|
+
return info;
|
|
27
|
+
})();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import intel from 'intel';
|
|
2
|
+
import get from 'lodash.get';
|
|
3
|
+
|
|
4
|
+
const log = intel.getLogger('browsertime.chrome.network');
|
|
5
|
+
|
|
6
|
+
export class NetworkManager {
|
|
7
|
+
constructor(cdpClient, options) {
|
|
8
|
+
this.maxTimeout = get(options, 'timeouts.pageCompleteCheck', 30_000);
|
|
9
|
+
this.idleTime = get(options, 'timeouts.networkIdle', 5000);
|
|
10
|
+
|
|
11
|
+
this.cdp = cdpClient.getRawClient();
|
|
12
|
+
this.inflight = 0;
|
|
13
|
+
this.lastRequestTimestamp;
|
|
14
|
+
this.lastResponseTimestamp;
|
|
15
|
+
|
|
16
|
+
this.cdp.Network.requestWillBeSent(() => {
|
|
17
|
+
this.inflight++;
|
|
18
|
+
this.lastRequestTimestamp = Date.now();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
this.cdp.Network.loadingFinished(() => {
|
|
22
|
+
this.inflight--;
|
|
23
|
+
this.lastResponseTimestamp = Date.now();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.cdp.Network.loadingFailed(() => {
|
|
27
|
+
this.inflight--;
|
|
28
|
+
this.lastResponseTimestamp = Date.now();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async waitForNetworkIdle() {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
// eslint-disable-next-line no-constant-condition
|
|
35
|
+
while (true) {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const sinceLastResponseRequest =
|
|
38
|
+
now - Math.max(this.lastResponseTimestamp, this.lastRequestTimestamp);
|
|
39
|
+
const sinceStart = now - startTime;
|
|
40
|
+
|
|
41
|
+
if (sinceLastResponseRequest >= this.idleTime) {
|
|
42
|
+
if (this.inflight > 0) {
|
|
43
|
+
log.info(
|
|
44
|
+
'Idle time without any request/responses. Inflight requests:' +
|
|
45
|
+
this.inflight
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (sinceStart >= this.maxTimeout) {
|
|
52
|
+
log.info(
|
|
53
|
+
'Timeout waiting for network. Inflight requests:' + this.inflight
|
|
54
|
+
);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await new Promise(r => setTimeout(r, 200));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -14,6 +14,7 @@ import { logging as _logging } from 'selenium-webdriver';
|
|
|
14
14
|
import { parse } from '../traceCategoriesParser.js';
|
|
15
15
|
import { pathToFolder } from '../../support/pathToFolder.js';
|
|
16
16
|
import { ChromeDevtoolsProtocol } from '../chromeDevtoolsProtocol.js';
|
|
17
|
+
import { NetworkManager } from '../networkManager.js';
|
|
17
18
|
import { Android, isAndroidConfigured } from '../../android/index.js';
|
|
18
19
|
import { getRenderBlocking } from './traceUtilities.js';
|
|
19
20
|
const unlink = promisify(_unlink);
|
|
@@ -502,4 +503,9 @@ export class Chromium {
|
|
|
502
503
|
async setCookies(url, cookies) {
|
|
503
504
|
return this.cdpClient.setCookies(url, cookies);
|
|
504
505
|
}
|
|
506
|
+
|
|
507
|
+
async waitForNetworkIdle() {
|
|
508
|
+
let network = new NetworkManager(this.cdpClient, this.options);
|
|
509
|
+
return network.waitForNetworkIdle();
|
|
510
|
+
}
|
|
505
511
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import intel from 'intel';
|
|
2
2
|
const log = intel.getLogger('browsertime.command.geckoprofiler');
|
|
3
3
|
export class GeckoProfiler {
|
|
4
|
-
constructor(GeckoProfiler, browser, index, options) {
|
|
4
|
+
constructor(GeckoProfiler, browser, index, options, result) {
|
|
5
5
|
this.GeckoProfiler = GeckoProfiler;
|
|
6
6
|
this.browser = browser;
|
|
7
7
|
this.index = index;
|
|
8
8
|
this.options = options;
|
|
9
|
+
this.result = result;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
async start() {
|
|
@@ -25,11 +26,7 @@ export class GeckoProfiler {
|
|
|
25
26
|
async stop() {
|
|
26
27
|
if (this.options.browser === 'firefox') {
|
|
27
28
|
if (this.options.firefox.geckoProfilerRecordingType === 'custom') {
|
|
28
|
-
|
|
29
|
-
.getDriver()
|
|
30
|
-
.executeScript('return document.documentURI;');
|
|
31
|
-
|
|
32
|
-
return this.GeckoProfiler.stop(this.index, url);
|
|
29
|
+
return this.GeckoProfiler.stop(this.index, this.result[0].url);
|
|
33
30
|
}
|
|
34
31
|
} else {
|
|
35
32
|
throw new Error('Geckoprofiler only works in Firefox');
|
|
@@ -154,7 +154,11 @@ export class Measure {
|
|
|
154
154
|
await this.engineDelegate.beforeStartIteration(this.browser, this.index);
|
|
155
155
|
}
|
|
156
156
|
this.numberOfVisitedPages++;
|
|
157
|
-
return this.browser.loadAndWait(
|
|
157
|
+
return this.browser.loadAndWait(
|
|
158
|
+
url,
|
|
159
|
+
this.pageCompleteCheck,
|
|
160
|
+
this.engineDelegate
|
|
161
|
+
);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
/**
|
|
@@ -229,7 +233,11 @@ export class Measure {
|
|
|
229
233
|
this.result[this.numberOfMeasuredPages].url = url;
|
|
230
234
|
try {
|
|
231
235
|
await this.engineDelegate.beforeEachURL(this.browser, url);
|
|
232
|
-
await this.browser.loadAndWait(
|
|
236
|
+
await this.browser.loadAndWait(
|
|
237
|
+
url,
|
|
238
|
+
this.pageCompleteCheck,
|
|
239
|
+
this.engineDelegate
|
|
240
|
+
);
|
|
233
241
|
return this.stop(url);
|
|
234
242
|
} catch (error) {
|
|
235
243
|
this.result[this.numberOfMeasuredPages].error = [error.name];
|
|
@@ -24,25 +24,40 @@ const defaults = {
|
|
|
24
24
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {
|
|
27
|
+
* @function timeout
|
|
28
|
+
* @description Wraps a promise with a timeout, rejecting the promise with a TimeoutError if it does not settle within the specified time.
|
|
29
|
+
*
|
|
30
|
+
* @param {Promise} promise - The promise to wrap with a timeout.
|
|
31
|
+
* @param {number} ms - The number of milliseconds to wait before timing out.
|
|
32
|
+
* @param {string} errorMessage - The error message for the TimeoutError.
|
|
33
|
+
*
|
|
34
|
+
* @returns {Promise} - A promise that resolves with the value of the input promise if it settles within time, or rejects with a TimeoutError otherwise.
|
|
32
35
|
*/
|
|
33
36
|
async function timeout(promise, ms, errorMessage) {
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
|
|
37
|
+
let timerId;
|
|
38
|
+
let finished = false;
|
|
39
|
+
|
|
40
|
+
// Create a new promise that rejects after `ms` milliseconds.
|
|
41
|
+
const timer = new Promise((_, reject) => {
|
|
42
|
+
timerId = setTimeout(() => {
|
|
43
|
+
if (!finished) {
|
|
44
|
+
// Reject with a TimeoutError if the input promise has not yet settled.
|
|
45
|
+
reject(new Error(errorMessage));
|
|
46
|
+
}
|
|
47
|
+
}, ms);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Race the input promise against the timer.
|
|
52
|
+
const result = await Promise.race([promise, timer]);
|
|
53
|
+
finished = true;
|
|
54
|
+
clearTimeout(timerId);
|
|
55
|
+
return result;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
finished = true;
|
|
58
|
+
clearTimeout(timerId);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
/**
|
|
@@ -132,14 +147,15 @@ export class SeleniumRunner {
|
|
|
132
147
|
|
|
133
148
|
async extraWait(pageCompleteCheck) {
|
|
134
149
|
await delay(this.options.beforePageCompleteWaitTime || 5000);
|
|
135
|
-
return this.
|
|
150
|
+
return this._waitOnPageCompleteCheck(pageCompleteCheck);
|
|
136
151
|
}
|
|
137
152
|
/**
|
|
138
153
|
* Wait for pageCompleteCheck to end before we return.
|
|
139
154
|
* @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
|
|
140
155
|
* @throws {UrlLoadError}
|
|
141
156
|
*/
|
|
142
|
-
|
|
157
|
+
|
|
158
|
+
async _waitOnPageCompleteCheck(pageCompleteCheck, url) {
|
|
143
159
|
const waitTime = this.options.pageCompleteWaitTime || 5000;
|
|
144
160
|
if (!pageCompleteCheck) {
|
|
145
161
|
pageCompleteCheck = this.options.pageCompleteCheckInactivity
|
|
@@ -209,7 +225,7 @@ export class SeleniumRunner {
|
|
|
209
225
|
* @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
|
|
210
226
|
* @throws {UrlLoadError}
|
|
211
227
|
*/
|
|
212
|
-
async loadAndWait(url, pageCompleteCheck) {
|
|
228
|
+
async loadAndWait(url, pageCompleteCheck, engine) {
|
|
213
229
|
const driver = this.driver;
|
|
214
230
|
// Browsers may normalize 'https://x.com' differently; in particular, Firefox normalizes to
|
|
215
231
|
// 'https://x.com/'. This is a first normalization attempt; there are deeper options that order
|
|
@@ -263,81 +279,86 @@ export class SeleniumRunner {
|
|
|
263
279
|
await driver.executeScript(navigate);
|
|
264
280
|
}
|
|
265
281
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
this.options.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
282
|
+
if (this.options.pageCompleteCheckNetworkIdle) {
|
|
283
|
+
return engine.waitForNetworkIdle(driver);
|
|
284
|
+
} else {
|
|
285
|
+
// If you run with default settings, the webdriver will give back
|
|
286
|
+
// control ASAP. Therefore you want to wait some extra time
|
|
287
|
+
// before you start to run your page complete check
|
|
288
|
+
this.options.pageLoadStrategy === 'none'
|
|
289
|
+
? await delay(this.options.pageCompleteCheckStartWait || 5000)
|
|
290
|
+
: await delay(2000);
|
|
291
|
+
|
|
292
|
+
// We give it a couple of times to finish loading, this makes it
|
|
293
|
+
// more stable in real case scenarios on slow servers.
|
|
294
|
+
let totalWaitTime = 0;
|
|
295
|
+
const tries = get(this.options, 'retries', 5) + 1;
|
|
296
|
+
for (let index = 0; index < tries; ++index) {
|
|
297
|
+
try {
|
|
298
|
+
await this._waitOnPageCompleteCheck(pageCompleteCheck, normalizedURI);
|
|
299
|
+
const newURI = new URL(
|
|
300
|
+
await driver.executeScript('return document.documentURI;')
|
|
301
|
+
).toString();
|
|
302
|
+
// If we use a SPA it could be that we don't test a new URL so just do one try
|
|
303
|
+
// and make sure your page complete check take care of other things
|
|
304
|
+
if (this.options.spa) {
|
|
305
|
+
break;
|
|
306
|
+
} else if (normalizedURI === startURI) {
|
|
307
|
+
// You are navigating to the current page
|
|
308
|
+
break;
|
|
309
|
+
} else if (normalizedURI.startsWith('data:text')) {
|
|
310
|
+
// Navigations between data/text seems to don't change the URI
|
|
311
|
+
break;
|
|
312
|
+
} else if (newURI === 'chrome-error://chromewebdata/') {
|
|
313
|
+
// This is the timeout URL for Chrome, just continue to try
|
|
314
|
+
throw new UrlLoadError(
|
|
315
|
+
`Could not load ${url} is the web page down?`,
|
|
316
|
+
url
|
|
317
|
+
);
|
|
318
|
+
} else if (newURI === startURI) {
|
|
319
|
+
const waitTime =
|
|
320
|
+
(this.options.retryWaitTime || 10_000) * (index + 1);
|
|
321
|
+
totalWaitTime += waitTime;
|
|
322
|
+
log.debug(
|
|
323
|
+
`URL ${url} failed to load, the ${
|
|
324
|
+
this.options.browser
|
|
325
|
+
} are still on ${startURI} , trying ${
|
|
326
|
+
tries - index - 1
|
|
327
|
+
} more time(s) but first wait for ${waitTime} ms.`
|
|
328
|
+
);
|
|
309
329
|
|
|
330
|
+
if (index === tries - 1) {
|
|
331
|
+
// If the last tries through an error, rethrow as before
|
|
332
|
+
const message = `Could not load ${url} - the navigation never happend after ${tries} tries and total wait time of ${totalWaitTime} ms`;
|
|
333
|
+
log.error(message);
|
|
334
|
+
throw new UrlLoadError(message, url);
|
|
335
|
+
} else {
|
|
336
|
+
// We add some wait time before we try again
|
|
337
|
+
await delay(waitTime);
|
|
338
|
+
log.info(
|
|
339
|
+
'Will check again if the browser has navigated to the page'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// We navigated to a new page, we don't need to test anymore
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
log.info(
|
|
348
|
+
`URL failed to load, trying ${tries - index - 1} more time(s): ${
|
|
349
|
+
error.message
|
|
350
|
+
}`
|
|
351
|
+
);
|
|
352
|
+
//
|
|
310
353
|
if (index === tries - 1) {
|
|
311
354
|
// If the last tries through an error, rethrow as before
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
355
|
+
log.error('Could not load URL %s', url, error);
|
|
356
|
+
throw new UrlLoadError('Failed to load ' + url, url, {
|
|
357
|
+
cause: error
|
|
358
|
+
});
|
|
315
359
|
} else {
|
|
316
|
-
|
|
317
|
-
await delay(waitTime);
|
|
318
|
-
log.info(
|
|
319
|
-
'Will check again if the browser has navigated to the page'
|
|
320
|
-
);
|
|
360
|
+
await delay(1000);
|
|
321
361
|
}
|
|
322
|
-
} else {
|
|
323
|
-
// We navigated to a new page, we don't need to test anymore
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
} catch (error) {
|
|
327
|
-
log.info(
|
|
328
|
-
`URL failed to load, trying ${tries - index - 1} more time(s): ${
|
|
329
|
-
error.message
|
|
330
|
-
}`
|
|
331
|
-
);
|
|
332
|
-
//
|
|
333
|
-
if (index === tries - 1) {
|
|
334
|
-
// If the last tries through an error, rethrow as before
|
|
335
|
-
log.error('Could not load URL %s', url, error);
|
|
336
|
-
throw new UrlLoadError('Failed to load ' + url, url, {
|
|
337
|
-
cause: error
|
|
338
|
-
});
|
|
339
|
-
} else {
|
|
340
|
-
await delay(1000);
|
|
341
362
|
}
|
|
342
363
|
}
|
|
343
364
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import intel from 'intel';
|
|
2
|
+
import get from 'lodash.get';
|
|
3
|
+
const log = intel.getLogger('browsertime.chrome.network');
|
|
4
|
+
|
|
5
|
+
export class NetworkManager {
|
|
6
|
+
constructor(bidi, browsingContextIds, options) {
|
|
7
|
+
this.bidi = bidi;
|
|
8
|
+
this.browsingContextIds = browsingContextIds;
|
|
9
|
+
this.maxTimeout = get(options, 'timeouts.pageCompleteCheck', 30_000);
|
|
10
|
+
this.idleTime = get(options, 'timeouts.networkIdle', 5000);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async waitForNetworkIdle() {
|
|
14
|
+
await this.bidi.subscribe(
|
|
15
|
+
'network.beforeRequestSent',
|
|
16
|
+
this.browsingContextIds
|
|
17
|
+
);
|
|
18
|
+
await this.bidi.subscribe(
|
|
19
|
+
'network.responseCompleted',
|
|
20
|
+
this.browsingContextIds
|
|
21
|
+
);
|
|
22
|
+
await this.bidi.subscribe('network.fetchError', this.browsingContextIds);
|
|
23
|
+
|
|
24
|
+
let inflight = 0;
|
|
25
|
+
let lastRequestTimestamp;
|
|
26
|
+
let lastResponseTimestamp;
|
|
27
|
+
this.ws = await this.bidi.socket;
|
|
28
|
+
this.ws.on('message', function (event) {
|
|
29
|
+
const { method } = JSON.parse(Buffer.from(event.toString()));
|
|
30
|
+
if (method) {
|
|
31
|
+
switch (method) {
|
|
32
|
+
case 'network.beforeRequestSent': {
|
|
33
|
+
inflight++;
|
|
34
|
+
lastRequestTimestamp = Date.now();
|
|
35
|
+
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case 'network.responseCompleted': {
|
|
39
|
+
inflight--;
|
|
40
|
+
lastResponseTimestamp = Date.now();
|
|
41
|
+
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'network.fetchError': {
|
|
45
|
+
inflight--;
|
|
46
|
+
lastResponseTimestamp = Date.now();
|
|
47
|
+
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
// No default
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
// eslint-disable-next-line no-constant-condition
|
|
57
|
+
while (true) {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const sinceLastResponseRequest =
|
|
60
|
+
now - Math.max(lastResponseTimestamp, lastRequestTimestamp);
|
|
61
|
+
const sinceStart = now - startTime;
|
|
62
|
+
|
|
63
|
+
if (sinceLastResponseRequest >= this.idleTime) {
|
|
64
|
+
if (inflight > 0) {
|
|
65
|
+
log.info(
|
|
66
|
+
'Idle time without any request/responses. Inflight requests:' +
|
|
67
|
+
inflight
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
await this.bidi.unsubscribe(
|
|
71
|
+
'network.beforeRequestSent',
|
|
72
|
+
this.browsingContextIds
|
|
73
|
+
);
|
|
74
|
+
await this.bidi.unsubscribe(
|
|
75
|
+
'network.responseCompleted',
|
|
76
|
+
this.browsingContextIds
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await this.bidi.unsubscribe(
|
|
80
|
+
'network.fetchError',
|
|
81
|
+
this.browsingContextIds
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sinceStart >= this.maxTimeout) {
|
|
88
|
+
log.info(
|
|
89
|
+
'Timeout waiting for network idle. Inflight requests:' + inflight
|
|
90
|
+
);
|
|
91
|
+
await this.bidi.unsubscribe(
|
|
92
|
+
'network.beforeRequestSent',
|
|
93
|
+
this.browsingContextIds
|
|
94
|
+
);
|
|
95
|
+
await this.bidi.unsubscribe(
|
|
96
|
+
'network.responseCompleted',
|
|
97
|
+
this.browsingContextIds
|
|
98
|
+
);
|
|
99
|
+
await this.bidi.unsubscribe(
|
|
100
|
+
'network.fetchError',
|
|
101
|
+
this.browsingContextIds
|
|
102
|
+
);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await new Promise(r => setTimeout(r, 200));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -12,6 +12,7 @@ import { findFiles } from '../../support/fileUtil.js';
|
|
|
12
12
|
import { GeckoProfiler } from '../geckoProfiler.js';
|
|
13
13
|
import { MemoryReport } from '../memoryReport.js';
|
|
14
14
|
import { PerfStats } from '../perfStats.js';
|
|
15
|
+
import { NetworkManager } from '../networkManager.js';
|
|
15
16
|
import { getHAR } from '../getHAR.js';
|
|
16
17
|
|
|
17
18
|
const log = intel.getLogger('browsertime.firefox');
|
|
@@ -323,4 +324,11 @@ export class Firefox {
|
|
|
323
324
|
* Before the browser is stopped/closed.
|
|
324
325
|
*/
|
|
325
326
|
async afterBrowserStopped() {}
|
|
327
|
+
|
|
328
|
+
async waitForNetworkIdle(driver) {
|
|
329
|
+
const windowId = await driver.getWindowHandle();
|
|
330
|
+
const bidi = await driver.getBidi();
|
|
331
|
+
let network = new NetworkManager(bidi, [windowId], this.options);
|
|
332
|
+
return network.waitForNetworkIdle();
|
|
333
|
+
}
|
|
326
334
|
}
|
package/lib/support/cli.js
CHANGED
|
@@ -176,6 +176,13 @@ export function parseCommandLine() {
|
|
|
176
176
|
'Timeout when waiting for page to complete loading, in milliseconds',
|
|
177
177
|
group: 'timeouts'
|
|
178
178
|
})
|
|
179
|
+
.option('timeouts.networkIdle', {
|
|
180
|
+
default: 5000,
|
|
181
|
+
type: 'number',
|
|
182
|
+
describe:
|
|
183
|
+
'Timeout when running pageCompleteCheckNetworkIdle, in milliseconds',
|
|
184
|
+
group: 'timeouts'
|
|
185
|
+
})
|
|
179
186
|
.option('chrome.args', {
|
|
180
187
|
describe:
|
|
181
188
|
'Extra command line arguments to pass to the Chrome process (e.g. --no-sandbox). ' +
|
|
@@ -911,6 +918,12 @@ export function parseCommandLine() {
|
|
|
911
918
|
type: 'boolean',
|
|
912
919
|
default: false
|
|
913
920
|
})
|
|
921
|
+
.option('pageCompleteCheckNetworkIdle', {
|
|
922
|
+
describe:
|
|
923
|
+
'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 ',
|
|
924
|
+
type: 'boolean',
|
|
925
|
+
default: false
|
|
926
|
+
})
|
|
914
927
|
.option('pageCompleteCheckPollTimeout', {
|
|
915
928
|
type: 'number',
|
|
916
929
|
default: 1500,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browsertime",
|
|
3
3
|
"description": "Get performance metrics from your web page using Browsertime.",
|
|
4
|
-
"version": "17.
|
|
4
|
+
"version": "17.18.1",
|
|
5
5
|
"bin": "./bin/browsertime.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@cypress/xvfb": "1.2.4",
|
|
9
9
|
"@devicefarmer/adbkit": "2.11.3",
|
|
10
|
-
"@sitespeed.io/chromedriver": "
|
|
10
|
+
"@sitespeed.io/chromedriver": "119.0.6045-59",
|
|
11
11
|
"@sitespeed.io/edgedriver": "115.0.1901-183",
|
|
12
12
|
"@sitespeed.io/geckodriver": "0.33.0",
|
|
13
13
|
"@sitespeed.io/throttle": "5.0.0",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lodash.merge": "4.6.2",
|
|
30
30
|
"lodash.pick": "4.4.0",
|
|
31
31
|
"lodash.set": "4.3.2",
|
|
32
|
-
"selenium-webdriver": "4.
|
|
32
|
+
"selenium-webdriver": "4.14.0",
|
|
33
33
|
"yargs": "17.7.2"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|