Skip to content

Commit

Permalink
Merge branch 'main' into edge-118
Browse files Browse the repository at this point in the history
  • Loading branch information
soulgalore authored Oct 31, 2023
2 parents 753cc73 + 049e815 commit 8a236ac
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 94 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ jobs:
run: ./bin/browsertime.js -b chrome --preWarmServer --xvfb http://127.0.0.1:3000/simple/
- name: Run test with screenshots
run: ./bin/browsertime.js -b chrome --screenshotLCP --screenshotLS --xvfb http://127.0.0.1:3000/simple/
- name: Run test with check network idle in Chrome
run: ./bin/browsertime.js -b chrome --pageCompleteCheckNetworkIdle --xvfb http://127.0.0.1:3000/simple/
- name: Run test with check network idle in Firefox
run: ./bin/browsertime.js -b firefox --pageCompleteCheckNetworkIdle --xvfb http://127.0.0.1:3000/simple/
- name: Run test with scripting.mjs
run: ./bin/browsertime.js -b chrome -n 1 --xvfb test/data/scripting/module.mjs
- name: Run test with scripting.cjs
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Browsertime changelog (we do [semantic versioning](https://semver.org))


## 17.18.0 - 2022-10-23
### Added
* 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).
* 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.
* 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)

### Fixed
* 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).
* 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.17.0 - 2022-10-11
### Added
* Firefox 118, Edge 117 and Chrome/Chromedriver 118 in the Docker container [#1996](https://github.com/sitespeedio/browsertime/pull/1996).
Expand Down
61 changes: 61 additions & 0 deletions lib/chrome/networkManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import intel from 'intel';
import get from 'lodash.get';

const log = intel.getLogger('browsertime.chrome.network');

export class NetworkManager {
constructor(cdpClient, options) {
this.maxTimeout = get(options, 'timeouts.pageCompleteCheck', 30_000);
this.idleTime = get(options, 'timeouts.networkIdle', 5000);

this.cdp = cdpClient.getRawClient();
this.inflight = 0;
this.lastRequestTimestamp;
this.lastResponseTimestamp;

this.cdp.Network.requestWillBeSent(() => {
this.inflight++;
this.lastRequestTimestamp = Date.now();
});

this.cdp.Network.loadingFinished(() => {
this.inflight--;
this.lastResponseTimestamp = Date.now();
});

this.cdp.Network.loadingFailed(() => {
this.inflight--;
this.lastResponseTimestamp = Date.now();
});
}

async waitForNetworkIdle() {
const startTime = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const now = Date.now();
const sinceLastResponseRequest =
now - Math.max(this.lastResponseTimestamp, this.lastRequestTimestamp);
const sinceStart = now - startTime;

if (sinceLastResponseRequest >= this.idleTime) {
if (this.inflight > 0) {
log.info(
'Idle time without any request/responses. Inflight requests:' +
this.inflight
);
}
break;
}

if (sinceStart >= this.maxTimeout) {
log.info(
'Timeout waiting for network. Inflight requests:' + this.inflight
);
break;
}

await new Promise(r => setTimeout(r, 200));
}
}
}
6 changes: 6 additions & 0 deletions lib/chrome/webdriver/chromium.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { logging as _logging } from 'selenium-webdriver';
import { parse } from '../traceCategoriesParser.js';
import { pathToFolder } from '../../support/pathToFolder.js';
import { ChromeDevtoolsProtocol } from '../chromeDevtoolsProtocol.js';
import { NetworkManager } from '../networkManager.js';
import { Android, isAndroidConfigured } from '../../android/index.js';
import { getRenderBlocking } from './traceUtilities.js';
const unlink = promisify(_unlink);
Expand Down Expand Up @@ -502,4 +503,9 @@ export class Chromium {
async setCookies(url, cookies) {
return this.cdpClient.setCookies(url, cookies);
}

async waitForNetworkIdle() {
let network = new NetworkManager(this.cdpClient, this.options);
return network.waitForNetworkIdle();
}
}
12 changes: 10 additions & 2 deletions lib/core/engine/command/measure.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ export class Measure {
await this.engineDelegate.beforeStartIteration(this.browser, this.index);
}
this.numberOfVisitedPages++;
return this.browser.loadAndWait(url, this.pageCompleteCheck);
return this.browser.loadAndWait(
url,
this.pageCompleteCheck,
this.engineDelegate
);
}

/**
Expand Down Expand Up @@ -229,7 +233,11 @@ export class Measure {
this.result[this.numberOfMeasuredPages].url = url;
try {
await this.engineDelegate.beforeEachURL(this.browser, url);
await this.browser.loadAndWait(url, this.pageCompleteCheck);
await this.browser.loadAndWait(
url,
this.pageCompleteCheck,
this.engineDelegate
);
return this.stop(url);
} catch (error) {
this.result[this.numberOfMeasuredPages].error = [error.name];
Expand Down
152 changes: 79 additions & 73 deletions lib/core/seleniumRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,15 @@ export class SeleniumRunner {

async extraWait(pageCompleteCheck) {
await delay(this.options.beforePageCompleteWaitTime || 5000);
return this.wait(pageCompleteCheck);
return this._waitOnPageCompleteCheck(pageCompleteCheck);
}
/**
* Wait for pageCompleteCheck to end before we return.
* @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
* @throws {UrlLoadError}
*/
async wait(pageCompleteCheck, url) {

async _waitOnPageCompleteCheck(pageCompleteCheck, url) {
const waitTime = this.options.pageCompleteWaitTime || 5000;
if (!pageCompleteCheck) {
pageCompleteCheck = this.options.pageCompleteCheckInactivity
Expand Down Expand Up @@ -224,7 +225,7 @@ export class SeleniumRunner {
* @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
* @throws {UrlLoadError}
*/
async loadAndWait(url, pageCompleteCheck) {
async loadAndWait(url, pageCompleteCheck, engine) {
const driver = this.driver;
// Browsers may normalize 'https://x.com' differently; in particular, Firefox normalizes to
// 'https://x.com/'. This is a first normalization attempt; there are deeper options that order
Expand Down Expand Up @@ -278,81 +279,86 @@ export class SeleniumRunner {
await driver.executeScript(navigate);
}

// If you run with default settings, the webdriver will give back
// control ASAP. Therefore you want to wait some extra time
// before you start to run your page complete check
this.options.pageLoadStrategy === 'none'
? await delay(this.options.pageCompleteCheckStartWait || 5000)
: await delay(2000);

// We give it a couple of times to finish loading, this makes it
// more stable in real case scenarios on slow servers.
let totalWaitTime = 0;
const tries = get(this.options, 'retries', 5) + 1;
for (let index = 0; index < tries; ++index) {
try {
await this.wait(pageCompleteCheck, normalizedURI);
const newURI = new URL(
await driver.executeScript('return document.documentURI;')
).toString();
// If we use a SPA it could be that we don't test a new URL so just do one try
// and make sure your page complete check take care of other things
if (this.options.spa) {
break;
} else if (normalizedURI === startURI) {
// You are navigating to the current page
break;
} else if (normalizedURI.startsWith('data:text')) {
// Navigations between data/text seems to don't change the URI
break;
} else if (newURI === 'chrome-error://chromewebdata/') {
// This is the timeout URL for Chrome, just continue to try
throw new UrlLoadError(
`Could not load ${url} is the web page down?`,
url
);
} else if (newURI === startURI) {
const waitTime = (this.options.retryWaitTime || 10_000) * (index + 1);
totalWaitTime += waitTime;
log.debug(
`URL ${url} failed to load, the ${
this.options.browser
} are still on ${startURI} , trying ${
tries - index - 1
} more time(s) but first wait for ${waitTime} ms.`
);
if (this.options.pageCompleteCheckNetworkIdle) {
return engine.waitForNetworkIdle(driver);
} else {
// If you run with default settings, the webdriver will give back
// control ASAP. Therefore you want to wait some extra time
// before you start to run your page complete check
this.options.pageLoadStrategy === 'none'
? await delay(this.options.pageCompleteCheckStartWait || 5000)
: await delay(2000);

// We give it a couple of times to finish loading, this makes it
// more stable in real case scenarios on slow servers.
let totalWaitTime = 0;
const tries = get(this.options, 'retries', 5) + 1;
for (let index = 0; index < tries; ++index) {
try {
await this._waitOnPageCompleteCheck(pageCompleteCheck, normalizedURI);
const newURI = new URL(
await driver.executeScript('return document.documentURI;')
).toString();
// If we use a SPA it could be that we don't test a new URL so just do one try
// and make sure your page complete check take care of other things
if (this.options.spa) {
break;
} else if (normalizedURI === startURI) {
// You are navigating to the current page
break;
} else if (normalizedURI.startsWith('data:text')) {
// Navigations between data/text seems to don't change the URI
break;
} else if (newURI === 'chrome-error://chromewebdata/') {
// This is the timeout URL for Chrome, just continue to try
throw new UrlLoadError(
`Could not load ${url} is the web page down?`,
url
);
} else if (newURI === startURI) {
const waitTime =
(this.options.retryWaitTime || 10_000) * (index + 1);
totalWaitTime += waitTime;
log.debug(
`URL ${url} failed to load, the ${
this.options.browser
} are still on ${startURI} , trying ${
tries - index - 1
} more time(s) but first wait for ${waitTime} ms.`
);

if (index === tries - 1) {
// If the last tries through an error, rethrow as before
const message = `Could not load ${url} - the navigation never happend after ${tries} tries and total wait time of ${totalWaitTime} ms`;
log.error(message);
throw new UrlLoadError(message, url);
} else {
// We add some wait time before we try again
await delay(waitTime);
log.info(
'Will check again if the browser has navigated to the page'
);
}
} else {
// We navigated to a new page, we don't need to test anymore
break;
}
} catch (error) {
log.info(
`URL failed to load, trying ${tries - index - 1} more time(s): ${
error.message
}`
);
//
if (index === tries - 1) {
// If the last tries through an error, rethrow as before
const message = `Could not load ${url} - the navigation never happend after ${tries} tries and total wait time of ${totalWaitTime} ms`;
log.error(message);
throw new UrlLoadError(message, url);
log.error('Could not load URL %s', url, error);
throw new UrlLoadError('Failed to load ' + url, url, {
cause: error
});
} else {
// We add some wait time before we try again
await delay(waitTime);
log.info(
'Will check again if the browser has navigated to the page'
);
await delay(1000);
}
} else {
// We navigated to a new page, we don't need to test anymore
break;
}
} catch (error) {
log.info(
`URL failed to load, trying ${tries - index - 1} more time(s): ${
error.message
}`
);
//
if (index === tries - 1) {
// If the last tries through an error, rethrow as before
log.error('Could not load URL %s', url, error);
throw new UrlLoadError('Failed to load ' + url, url, {
cause: error
});
} else {
await delay(1000);
}
}
}
Expand Down
Loading

0 comments on commit 8a236ac

Please sign in to comment.