browsertime 27.1.0 → 27.3.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 +16 -0
- package/bin/browsertime.js +1 -1
- package/bin/browsertimeWebPageReplay.js +1 -1
- package/lib/chrome/webdriver/chromium.js +13 -0
- package/lib/core/engine/command/measure.js +4 -5
- package/lib/core/engine/commands.js +14 -26
- package/lib/core/engine/context.js +4 -9
- package/lib/core/engine/index.js +6 -2
- package/lib/core/seleniumRunner.js +1 -2
- package/lib/safari/safariInspectorClient.js +0 -1
- package/lib/screenshot/index.js +1 -1
- package/lib/support/browserScript.js +2 -4
- package/lib/support/cli.js +41 -20
- package/lib/support/har/index.js +1 -2
- package/lib/support/helpTopics.js +174 -0
- package/lib/support/util.js +46 -0
- package/lib/video/postprocessing/finetune/addTextToVideo.js +1 -1
- package/package.json +16 -15
- package/scripting.d.ts +21 -0
- package/types/android/index.d.ts +2 -2
- package/types/android/index.d.ts.map +1 -1
- package/types/core/engine/command/measure.d.ts +6 -6
- package/types/core/engine/command/measure.d.ts.map +1 -1
- package/types/core/engine/command/simpleperf.d.ts +1 -1
- package/types/core/engine/command/simpleperf.d.ts.map +1 -1
- package/types/core/engine/commands.d.ts +51 -48
- package/types/core/engine/commands.d.ts.map +1 -1
- package/types/core/engine/context.d.ts +9 -8
- package/types/core/engine/context.d.ts.map +1 -1
- package/types/support/tcpdump.d.ts +1 -1
- package/types/support/tcpdump.d.ts.map +1 -1
- package/types/support/util.d.ts +5 -0
- package/types/support/util.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/convertFps.d.ts +1 -1
- package/types/video/postprocessing/finetune/convertFps.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/removeOrange.d.ts +1 -1
- package/types/video/postprocessing/finetune/removeOrange.d.ts.map +1 -1
- package/types/video/postprocessing/visualmetrics/visualMetrics.d.ts +1 -1
- package/types/video/postprocessing/visualmetrics/visualMetrics.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/convert.d.ts +1 -1
- package/types/video/screenRecording/desktop/convert.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/desktopRecorder.d.ts +2 -2
- package/types/video/screenRecording/desktop/ffmpegRecorder.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts.map +1 -1
- package/types/video/screenRecording/desktop/osx/getScreen.d.ts +1 -1
- package/types/video/screenRecording/desktop/osx/getScreen.d.ts.map +1 -1
- package/types/video/screenRecording/iosSimulator/convertToMp4.d.ts +1 -1
- package/types/video/screenRecording/iosSimulator/convertToMp4.d.ts.map +1 -1
- package/types/video/screenRecording/iosSimulator/recorder.d.ts +2 -6
- package/types/video/screenRecording/iosSimulator/recorder.d.ts.map +1 -1
- package/types/scripting.d.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
+
## 27.3.0 - 2026-05-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
* `browsertime --help` is now navigable by topic. The default view shows a short curated list of common options plus the available topic names. `browsertime --help <topic>` shows just that topic, and `browsertime --help-all` reproduces the historical full dump for scripts and power users. The old single-screen dump of every option from all 13 topic groups was overwhelming for new users and slow to skim; the topic-filtered view follows the curl/ffmpeg pattern [#2467](https://github.com/sitespeedio/browsertime/pull/2467).
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
* TypeScript types and IntelliSense for navigation scripts are tightened across the script-facing surface. A new `BrowsertimeScript` type alias lets scripts annotate their default export and get both `context` and `commands` typed in one line. The high-level command helpers (`navigate`, `find`, `getText`, `fill`, `hover`, `press`, `getTitle`, `waitForUrl`, …) now surface their JSDoc parameter and return types in IntelliSense instead of being typed as `Function`, and `commands.find` resolves to a real `WebElement` so chained calls get completion from selenium-webdriver [#2476](https://github.com/sitespeedio/browsertime/pull/2476). `context.options`, `context.result`, and `context.taskData` are typed as `Record<string, any>` so editors offer completion on `context.options.` etc. [#2478](https://github.com/sitespeedio/browsertime/pull/2478). `commands.mouse` is now a proper shape (selector typed as `string`, `singleClick`'s options surface `waitForNavigation`) instead of an anonymous object literal, and `measure.stop` / `measure.clickAndMeasure` / `measure.stopAsError` return types are explicit (`Promise<void>` / `Promise<unknown>`) instead of `Promise<any>` [#2479](https://github.com/sitespeedio/browsertime/pull/2479).
|
|
10
|
+
* How Browsertime publishes its TypeScript types is now safer. `scripting.d.ts` moved to the repo root next to `package.json` so it can't be clobbered by a future `tsc` outDir change, the `types` condition comes first under `exports` to match TypeScript's documented resolution rules, and `publint` runs as part of `prepublishOnly` so common package.json mistakes fail before a release goes out [#2477](https://github.com/sitespeedio/browsertime/pull/2477).
|
|
11
|
+
* Replaced the direct `lodash.merge` dependency with a small in-tree `merge()` helper in `lib/support/util.js`. The helper matches lodash's actual semantics for the cases Browsertime relies on — arrays merged by index, `undefined` source values skipped unless the key is missing on the target, `Date`/`RegExp`/class instances passed by reference, prototype-pollution keys rejected — which finishes the lodash unbundling the same way sitespeed.io did [#2468](https://github.com/sitespeedio/browsertime/pull/2468).
|
|
12
|
+
* Bumped dependencies: `chrome-har` 1.3.1 [#2469](https://github.com/sitespeedio/browsertime/pull/2469), `chrome-remote-interface` and `selenium-webdriver` [#2475](https://github.com/sitespeedio/browsertime/pull/2475), `@sitespeed.io/log` 2.0 [#2471](https://github.com/sitespeedio/browsertime/pull/2471), `chromedriver` / `edgedriver` / `geckodriver` [#2472](https://github.com/sitespeedio/browsertime/pull/2472).
|
|
13
|
+
|
|
14
|
+
## 27.2.0 - 2026-05-12
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
* Surface the page-level `recalculateStyle` summary (beforeFCP / beforeLCP element counts + durations) on the first HAR page as `_renderBlocking.recalculateStyle`. The per-request `_renderBlocking` map already projected onto each entry stays where it is — the page-level summary is what powers the "Elements that needed recalculate style before FCP" view and was previously only reachable via `browsertime.json` [#2466](https://github.com/sitespeedio/browsertime/pull/2466).
|
|
18
|
+
|
|
3
19
|
## 27.1.0 - 2026-05-06
|
|
4
20
|
|
|
5
21
|
### Added
|
package/bin/browsertime.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
4
3
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
5
4
|
import path from 'node:path';
|
|
@@ -13,6 +12,7 @@ import { configure } from '../lib/support/logging.js';
|
|
|
13
12
|
import { parseCommandLine } from '../lib/support/cli.js';
|
|
14
13
|
import { StorageManager } from '../lib/support/storageManager.js';
|
|
15
14
|
import { loadScript } from '../lib/support/engineUtils.js';
|
|
15
|
+
import { merge } from '../lib/support/util.js';
|
|
16
16
|
import { setProperty, getProperty } from '../lib/support/util.js';
|
|
17
17
|
import { isAndroidConfigured } from '../lib/android/index.js';
|
|
18
18
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import yargs from 'yargs';
|
|
4
3
|
import { hideBin } from 'yargs/helpers';
|
|
5
4
|
|
|
6
5
|
import { Engine } from '../lib/core/engine/index.js';
|
|
7
6
|
import { configure as logging } from '../lib/support/logging.js';
|
|
7
|
+
import { merge } from '../lib/support/util.js';
|
|
8
8
|
|
|
9
9
|
async function runBrowsertime() {
|
|
10
10
|
let yargsInstance = yargs(hideBin(process.argv));
|
|
@@ -415,6 +415,19 @@ export class Chromium {
|
|
|
415
415
|
render.renderBlockingInfo[harRequest.request.url];
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
|
+
// Also stamp the page-level summary (recalculate-style
|
|
419
|
+
// elements + duration before FCP/LCP) onto the HAR's page
|
|
420
|
+
// object so HAR-only consumers can pick it up without
|
|
421
|
+
// needing browsertime.json. The per-request map is already
|
|
422
|
+
// projected onto each entry above, so we keep this payload
|
|
423
|
+
// to just the summary fields.
|
|
424
|
+
const harPage =
|
|
425
|
+
this.hars[index - 1].log.pages && this.hars[index - 1].log.pages[0];
|
|
426
|
+
if (harPage && render.renderBlocking.recalculateStyle) {
|
|
427
|
+
harPage._renderBlocking = {
|
|
428
|
+
recalculateStyle: render.renderBlocking.recalculateStyle
|
|
429
|
+
};
|
|
430
|
+
}
|
|
418
431
|
}
|
|
419
432
|
|
|
420
433
|
result.renderBlocking.requests = render.renderBlockingInfo;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
|
-
import merge from 'lodash.merge';
|
|
4
3
|
import { timestamp as _timestamp } from '../../../support/engineUtils.js';
|
|
5
4
|
import { pathToFolder } from '../../../support/pathToFolder.js';
|
|
6
5
|
import {
|
|
@@ -9,7 +8,7 @@ import {
|
|
|
9
8
|
} from '../../../support/userTiming.js';
|
|
10
9
|
import { isAndroidConfigured, Android } from '../../../android/index.js';
|
|
11
10
|
import { TCPDump } from '../../../support/tcpdump.js';
|
|
12
|
-
import { getProperty } from '../../../support/util.js';
|
|
11
|
+
import { getProperty, merge } from '../../../support/util.js';
|
|
13
12
|
import { MeasureScreenshots } from './measure/screenshots.js';
|
|
14
13
|
import { MeasureVideo } from './measure/video.js';
|
|
15
14
|
const log = getLogger('browsertime.command.measure');
|
|
@@ -329,7 +328,7 @@ export class Measure {
|
|
|
329
328
|
*
|
|
330
329
|
* @async
|
|
331
330
|
* @param {string} errorMessage - The message about the error. This will end up on the HTML report for sitespeed.io so give it a good message so you know what's gone wrong.
|
|
332
|
-
* @returns {Promise} A promise that resolves when the stop process has completed.
|
|
331
|
+
* @returns {Promise<void>} A promise that resolves when the stop process has completed.
|
|
333
332
|
* @since 21.2.0
|
|
334
333
|
*/
|
|
335
334
|
async stopAsError(errorMessage) {
|
|
@@ -362,7 +361,7 @@ export class Measure {
|
|
|
362
361
|
* @async
|
|
363
362
|
* @param {string} alias - The friendly name for this measurement.
|
|
364
363
|
* @param {string} selector - The CSS selector of the element to click.
|
|
365
|
-
* @returns {Promise} A promise that resolves with the collected metrics data.
|
|
364
|
+
* @returns {Promise<unknown>} A promise that resolves with the collected metrics data.
|
|
366
365
|
* @throws {Error} Throws an error if the element is not found or the measurement fails.
|
|
367
366
|
*/
|
|
368
367
|
async clickAndMeasure(alias, selector) {
|
|
@@ -379,7 +378,7 @@ export class Measure {
|
|
|
379
378
|
* @async
|
|
380
379
|
* @param {string} testedStartUrl - The URL that was initially tested. If not provided, it will be obtained from the browser.
|
|
381
380
|
* @throws {Error} Throws an error if there are issues in stopping the measurement or collecting data.
|
|
382
|
-
* @returns {Promise} A promise that resolves with the collected metrics data.
|
|
381
|
+
* @returns {Promise<unknown>} A promise that resolves with the collected metrics data.
|
|
383
382
|
*/
|
|
384
383
|
async stop(testedStartUrl) {
|
|
385
384
|
log.debug('Stop measuring');
|
|
@@ -227,10 +227,10 @@ export class Commands {
|
|
|
227
227
|
* Navigates to a specified URL and handles additional setup for a page visit.
|
|
228
228
|
* @async
|
|
229
229
|
* @example await commands.navigate('https://www.example.org');
|
|
230
|
-
* @type {Function}
|
|
231
230
|
* @param {string} url - The URL to navigate to.
|
|
232
231
|
* @throws {Error} Throws an error if navigation or setup fails.
|
|
233
232
|
* @returns {Promise<void>} A promise that resolves when the navigation and setup are
|
|
233
|
+
* @type {(url: string) => Promise<void>}
|
|
234
234
|
*/
|
|
235
235
|
this.navigate = measure._navigate.bind(measure);
|
|
236
236
|
|
|
@@ -244,7 +244,7 @@ export class Commands {
|
|
|
244
244
|
* Add a text that will be an error attached to the current page.
|
|
245
245
|
* @example await commands.error('My error message');
|
|
246
246
|
* @param {string} message - The error message.
|
|
247
|
-
* @type {
|
|
247
|
+
* @type {(message: string) => void}
|
|
248
248
|
*/
|
|
249
249
|
this.error = measure._error.bind(measure);
|
|
250
250
|
|
|
@@ -252,7 +252,7 @@ export class Commands {
|
|
|
252
252
|
* Mark this run as an failure. Add a message that explains the failure.
|
|
253
253
|
* @example await commands.markAsFailure('My failure message');
|
|
254
254
|
* @param {string} message - The message attached as a failure
|
|
255
|
-
* @type {
|
|
255
|
+
* @type {(message: string) => void}
|
|
256
256
|
*/
|
|
257
257
|
this.markAsFailure = measure._failure.bind(measure);
|
|
258
258
|
|
|
@@ -357,10 +357,6 @@ export class Commands {
|
|
|
357
357
|
*/
|
|
358
358
|
this.debug = new Debug(browser, options);
|
|
359
359
|
|
|
360
|
-
/**
|
|
361
|
-
* Interact with the page using the mouse.
|
|
362
|
-
* @type {Object}
|
|
363
|
-
*/
|
|
364
360
|
const singleClickInstance = new SingleClick(
|
|
365
361
|
browser,
|
|
366
362
|
pageCompleteCheck,
|
|
@@ -375,6 +371,16 @@ export class Commands {
|
|
|
375
371
|
const mouseMoveInstance = new MouseMove(browser, options);
|
|
376
372
|
const clickAndHoldInstance = new ClickAndHold(browser);
|
|
377
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Interact with the page using the mouse.
|
|
376
|
+
* @type {{
|
|
377
|
+
* moveTo: (selector: string) => Promise<void>,
|
|
378
|
+
* singleClick: (selector: string, mouseOptions?: { waitForNavigation?: boolean }) => Promise<void>,
|
|
379
|
+
* doubleClick: (selector: string) => Promise<void>,
|
|
380
|
+
* contextClick: (selector: string) => Promise<void>,
|
|
381
|
+
* clickAndHold: ClickAndHold
|
|
382
|
+
* }}
|
|
383
|
+
*/
|
|
378
384
|
this.mouse = {
|
|
379
385
|
moveTo: async selector => mouseMoveInstance.run(selector),
|
|
380
386
|
singleClick: async (selector, mouseOptions) =>
|
|
@@ -485,7 +491,6 @@ export class Commands {
|
|
|
485
491
|
* @param {string} text - The text to type into the element.
|
|
486
492
|
* @returns {Promise<void>} A promise that resolves when the text has been added.
|
|
487
493
|
* @throws {Error} Throws an error if the element is not found.
|
|
488
|
-
* @type {Function}
|
|
489
494
|
*/
|
|
490
495
|
this.type = async (selector, text) => {
|
|
491
496
|
return this.addText(selector, text);
|
|
@@ -502,7 +507,7 @@ export class Commands {
|
|
|
502
507
|
* @param {boolean} [options.visible=false] - If true, waits for the element to be visible.
|
|
503
508
|
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
|
|
504
509
|
* @throws {Error} Throws an error if the element is not found.
|
|
505
|
-
* @type {
|
|
510
|
+
* @type {(selector: string, options?: { timeout?: number, visible?: boolean }) => Promise<import('selenium-webdriver').WebElement>}
|
|
506
511
|
*/
|
|
507
512
|
this.find = this.element.find.bind(this.element);
|
|
508
513
|
|
|
@@ -518,7 +523,6 @@ export class Commands {
|
|
|
518
523
|
* @param {Object} [existsOptions] - Options.
|
|
519
524
|
* @param {number} [existsOptions.timeout=0] - Maximum time to wait for the element. Default 0 (no wait).
|
|
520
525
|
* @returns {Promise<boolean>} True if the element exists.
|
|
521
|
-
* @type {Function}
|
|
522
526
|
*/
|
|
523
527
|
this.exists = async (selector, existsOptions = {}) => {
|
|
524
528
|
const { locator } = parseSelector(selector);
|
|
@@ -552,7 +556,6 @@ export class Commands {
|
|
|
552
556
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
553
557
|
* @returns {Promise<string>} The visible text content.
|
|
554
558
|
* @throws {Error} Throws an error if the element is not found.
|
|
555
|
-
* @type {Function}
|
|
556
559
|
*/
|
|
557
560
|
this.getText = async selector => {
|
|
558
561
|
const element = await findElement(selector);
|
|
@@ -567,7 +570,6 @@ export class Commands {
|
|
|
567
570
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
568
571
|
* @returns {Promise<string>} The element's value.
|
|
569
572
|
* @throws {Error} Throws an error if the element is not found.
|
|
570
|
-
* @type {Function}
|
|
571
573
|
*/
|
|
572
574
|
this.getValue = async selector => {
|
|
573
575
|
const element = await findElement(selector);
|
|
@@ -581,7 +583,6 @@ export class Commands {
|
|
|
581
583
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
582
584
|
* @returns {Promise<boolean>} True if the element is visible.
|
|
583
585
|
* @throws {Error} Throws an error if the element is not found.
|
|
584
|
-
* @type {Function}
|
|
585
586
|
*/
|
|
586
587
|
this.isVisible = async selector => {
|
|
587
588
|
const element = await findElement(selector);
|
|
@@ -597,7 +598,6 @@ export class Commands {
|
|
|
597
598
|
* @param {string} attribute - The attribute name.
|
|
598
599
|
* @returns {Promise<string>} The attribute value.
|
|
599
600
|
* @throws {Error} Throws an error if the element is not found.
|
|
600
|
-
* @type {Function}
|
|
601
601
|
*/
|
|
602
602
|
this.getAttribute = async (selector, attribute) => {
|
|
603
603
|
const element = await findElement(selector);
|
|
@@ -611,7 +611,6 @@ export class Commands {
|
|
|
611
611
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
612
612
|
* @returns {Promise<boolean>} True if the element is enabled.
|
|
613
613
|
* @throws {Error} Throws an error if the element is not found.
|
|
614
|
-
* @type {Function}
|
|
615
614
|
*/
|
|
616
615
|
this.isEnabled = async selector => {
|
|
617
616
|
const element = await findElement(selector);
|
|
@@ -625,7 +624,6 @@ export class Commands {
|
|
|
625
624
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
626
625
|
* @returns {Promise<boolean>} True if the element is checked/selected.
|
|
627
626
|
* @throws {Error} Throws an error if the element is not found.
|
|
628
|
-
* @type {Function}
|
|
629
627
|
*/
|
|
630
628
|
this.isChecked = async selector => {
|
|
631
629
|
const element = await findElement(selector);
|
|
@@ -640,7 +638,6 @@ export class Commands {
|
|
|
640
638
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
641
639
|
* @returns {Promise<void>} A promise that resolves when the element is cleared.
|
|
642
640
|
* @throws {Error} Throws an error if the element is not found.
|
|
643
|
-
* @type {Function}
|
|
644
641
|
*/
|
|
645
642
|
this.clear = async selector => {
|
|
646
643
|
const { locator } = parseSelector(selector);
|
|
@@ -665,7 +662,6 @@ export class Commands {
|
|
|
665
662
|
* @param {Object<string, string>} fields - An object mapping selectors to values.
|
|
666
663
|
* @returns {Promise<void>} A promise that resolves when all fields are filled.
|
|
667
664
|
* @throws {Error} Throws an error if any element is not found.
|
|
668
|
-
* @type {Function}
|
|
669
665
|
*/
|
|
670
666
|
this.fill = async fields => {
|
|
671
667
|
for (const [selector, text] of Object.entries(fields)) {
|
|
@@ -682,7 +678,6 @@ export class Commands {
|
|
|
682
678
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
683
679
|
* @returns {Promise<void>}
|
|
684
680
|
* @throws {Error} Throws an error if the element is not found.
|
|
685
|
-
* @type {Function}
|
|
686
681
|
*/
|
|
687
682
|
this.hover = async selector => {
|
|
688
683
|
return this.mouse.moveTo(selector);
|
|
@@ -697,7 +692,6 @@ export class Commands {
|
|
|
697
692
|
* @example await commands.press('Escape');
|
|
698
693
|
* @param {string} key - The key name to press (e.g. 'Enter', 'Tab', 'Escape').
|
|
699
694
|
* @returns {Promise<void>}
|
|
700
|
-
* @type {Function}
|
|
701
695
|
*/
|
|
702
696
|
this.press = async key => {
|
|
703
697
|
const keyValue = webdriver.Key[key.toUpperCase()] || key;
|
|
@@ -711,7 +705,6 @@ export class Commands {
|
|
|
711
705
|
* @async
|
|
712
706
|
* @example const title = await commands.getTitle();
|
|
713
707
|
* @returns {Promise<string>} The page title.
|
|
714
|
-
* @type {Function}
|
|
715
708
|
*/
|
|
716
709
|
this.getTitle = async () => {
|
|
717
710
|
return browser.getDriver().getTitle();
|
|
@@ -722,7 +715,6 @@ export class Commands {
|
|
|
722
715
|
* @async
|
|
723
716
|
* @example const url = await commands.getUrl();
|
|
724
717
|
* @returns {Promise<string>} The current URL.
|
|
725
|
-
* @type {Function}
|
|
726
718
|
*/
|
|
727
719
|
this.getUrl = async () => {
|
|
728
720
|
return browser.getDriver().getCurrentUrl();
|
|
@@ -736,7 +728,6 @@ export class Commands {
|
|
|
736
728
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
737
729
|
* @returns {Promise<void>}
|
|
738
730
|
* @throws {Error} Throws an error if the element is not found.
|
|
739
|
-
* @type {Function}
|
|
740
731
|
*/
|
|
741
732
|
this.check = async selector => {
|
|
742
733
|
const element = await findElement(selector);
|
|
@@ -754,7 +745,6 @@ export class Commands {
|
|
|
754
745
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
755
746
|
* @returns {Promise<void>}
|
|
756
747
|
* @throws {Error} Throws an error if the element is not found.
|
|
757
|
-
* @type {Function}
|
|
758
748
|
*/
|
|
759
749
|
this.uncheck = async selector => {
|
|
760
750
|
const element = await findElement(selector);
|
|
@@ -772,7 +762,6 @@ export class Commands {
|
|
|
772
762
|
* @param {string} selector - The CSS selector or prefixed selector.
|
|
773
763
|
* @returns {Promise<void>}
|
|
774
764
|
* @throws {Error} Throws an error if the element is not found.
|
|
775
|
-
* @type {Function}
|
|
776
765
|
*/
|
|
777
766
|
this.scrollIntoView = async selector => {
|
|
778
767
|
const element = await findElement(selector);
|
|
@@ -795,7 +784,6 @@ export class Commands {
|
|
|
795
784
|
* @param {number} [urlOptions.timeout=10000] - Maximum time to wait in milliseconds.
|
|
796
785
|
* @returns {Promise<void>}
|
|
797
786
|
* @throws {Error} Throws an error if the URL does not match within the timeout.
|
|
798
|
-
* @type {Function}
|
|
799
787
|
*/
|
|
800
788
|
this.waitForUrl = async (pattern, urlOptions = {}) => {
|
|
801
789
|
const timeout = urlOptions.timeout ?? 10_000;
|
|
@@ -30,13 +30,13 @@ export class Context {
|
|
|
30
30
|
) {
|
|
31
31
|
/**
|
|
32
32
|
* This is the yargs object you get from the cli. If you add --my.id you can get that using options.my.id.
|
|
33
|
-
* @type {
|
|
33
|
+
* @type {Record<string, any>}
|
|
34
34
|
*/
|
|
35
35
|
this.options = options;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Here the result from each run is stored.
|
|
39
|
-
* @type {
|
|
39
|
+
* @type {Record<string, any>}
|
|
40
40
|
*/
|
|
41
41
|
this.result = result;
|
|
42
42
|
|
|
@@ -58,7 +58,8 @@ export class Context {
|
|
|
58
58
|
this.storageManager = storageManager;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Scratch space shared between commands within a single iteration.
|
|
62
|
+
* @type {Record<string, any>}
|
|
62
63
|
*/
|
|
63
64
|
this.taskData = {};
|
|
64
65
|
|
|
@@ -68,13 +69,7 @@ export class Context {
|
|
|
68
69
|
* @type {{webdriver: WebDriverClass, driver: WebDriverInstance}}
|
|
69
70
|
*/
|
|
70
71
|
this.selenium = {
|
|
71
|
-
/**
|
|
72
|
-
* @typedef {typeof import('selenium-webdriver')} WebDriverClass
|
|
73
|
-
*/
|
|
74
72
|
webdriver,
|
|
75
|
-
/**
|
|
76
|
-
* @typedef {import('selenium-webdriver').WebDriver} WebDriverInstance
|
|
77
|
-
*/
|
|
78
73
|
driver: instantiatedDriver
|
|
79
74
|
};
|
|
80
75
|
}
|
package/lib/core/engine/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { arch as _arch } from 'node:os';
|
|
2
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
|
-
import merge from 'lodash.merge';
|
|
4
3
|
import { execaCommand as command } from 'execa';
|
|
5
4
|
import { StorageManager } from '../../support/storageManager.js';
|
|
6
5
|
import { Firefox } from '../../firefox/webdriver/firefox.js';
|
|
@@ -10,7 +9,12 @@ import {
|
|
|
10
9
|
addConnectivity,
|
|
11
10
|
removeConnectivity
|
|
12
11
|
} from '../../connectivity/index.js';
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
logResultLogLine,
|
|
14
|
+
getProperty,
|
|
15
|
+
toArray,
|
|
16
|
+
merge
|
|
17
|
+
} from '../../support/util.js';
|
|
14
18
|
import {
|
|
15
19
|
getFullyLoaded,
|
|
16
20
|
getMainDocumentTimings,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { Condition } from 'selenium-webdriver';
|
|
4
3
|
import { isAndroidConfigured } from '../android/index.js';
|
|
5
4
|
import { UrlLoadError, BrowserError, TimeoutError } from '../support/errors.js';
|
|
@@ -8,7 +7,7 @@ import { getViewPort } from '../support/getViewPort.js';
|
|
|
8
7
|
import { defaultPageCompleteCheck } from './pageCompleteChecks/defaultPageCompleteCheck.js';
|
|
9
8
|
import { pageCompleteCheckByInactivity } from './pageCompleteChecks/pageCompleteCheckByInactivity.js';
|
|
10
9
|
import { spaInactivity as spaCheck } from './pageCompleteChecks/spaInactivity.js';
|
|
11
|
-
import { getProperty } from '../support/util.js';
|
|
10
|
+
import { getProperty, merge } from '../support/util.js';
|
|
12
11
|
const log = getLogger('browsertime');
|
|
13
12
|
|
|
14
13
|
const defaults = {
|
package/lib/screenshot/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
4
3
|
import { screenshotDefaults } from './defaults.js';
|
|
5
4
|
import {
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
saveJpg
|
|
9
8
|
} from '../support/images/index.js';
|
|
10
9
|
import { loadCustomJimp } from './loadCustomJimp.js';
|
|
10
|
+
import { merge } from '../support/util.js';
|
|
11
11
|
|
|
12
12
|
const SCREENSHOT_DIR = 'screenshots';
|
|
13
13
|
const log = getLogger('browsertime.screenshot');
|
|
@@ -141,8 +141,7 @@ async function findAndParseScripts(root, category) {
|
|
|
141
141
|
const categoryName = path.basename(dirPath);
|
|
142
142
|
|
|
143
143
|
return scriptsFromDirectory(dirPath).then(scripts => {
|
|
144
|
-
const categories = {};
|
|
145
|
-
categories[categoryName] = scripts;
|
|
144
|
+
const categories = { [categoryName]: scripts };
|
|
146
145
|
return categories;
|
|
147
146
|
});
|
|
148
147
|
}
|
|
@@ -154,8 +153,7 @@ async function findAndParseScripts(root, category) {
|
|
|
154
153
|
let scripts = {};
|
|
155
154
|
|
|
156
155
|
scripts[name] = generateScriptObject(name, root, content);
|
|
157
|
-
let categories = {};
|
|
158
|
-
categories[category] = scripts;
|
|
156
|
+
let categories = { [category]: scripts };
|
|
159
157
|
|
|
160
158
|
return categories;
|
|
161
159
|
})
|
package/lib/support/cli.js
CHANGED
|
@@ -14,6 +14,12 @@ import { geckoProfilerDefaults } from '../firefox/settings/geckoProfilerDefaults
|
|
|
14
14
|
import { findUpSync } from './fileUtil.js';
|
|
15
15
|
import { setProperty, getProperty } from './util.js';
|
|
16
16
|
import { execaSync } from 'execa';
|
|
17
|
+
import {
|
|
18
|
+
classifyHelp,
|
|
19
|
+
captureTopics,
|
|
20
|
+
applyHelpFilter,
|
|
21
|
+
buildEpilog
|
|
22
|
+
} from './helpTopics.js';
|
|
17
23
|
|
|
18
24
|
const configPath = findUpSync(['.browsertime.json']);
|
|
19
25
|
|
|
@@ -152,7 +158,16 @@ export function parseCommandLine() {
|
|
|
152
158
|
arg === '--android' ? '--android.enabled' : arg
|
|
153
159
|
);
|
|
154
160
|
|
|
155
|
-
|
|
161
|
+
// Detect --help <topic> / --help-all up front and rewrite them to a
|
|
162
|
+
// bare --help so yargs still triggers its normal help+exit flow.
|
|
163
|
+
const rawArgs = hideBin(argvFix);
|
|
164
|
+
const help = classifyHelp(rawArgs);
|
|
165
|
+
|
|
166
|
+
let yargsInstance = yargs(help.args);
|
|
167
|
+
// Wrap .option() to record each option's key under its inline
|
|
168
|
+
// `group: '<topic>'` field. Browsertime already annotates every option
|
|
169
|
+
// with a group, so this gives us a topic -> keys map for free.
|
|
170
|
+
const topicKeys = captureTopics(yargsInstance);
|
|
156
171
|
let validated = yargsInstance
|
|
157
172
|
.parserConfiguration({
|
|
158
173
|
'camel-case-expansion': false,
|
|
@@ -923,76 +938,76 @@ export function parseCommandLine() {
|
|
|
923
938
|
type: 'boolean',
|
|
924
939
|
default: false,
|
|
925
940
|
describe: 'Save one screenshot per iteration.',
|
|
926
|
-
group: '
|
|
941
|
+
group: 'screenshot'
|
|
927
942
|
})
|
|
928
943
|
.option('screenshotLCP', {
|
|
929
944
|
type: 'boolean',
|
|
930
945
|
default: false,
|
|
931
946
|
describe:
|
|
932
947
|
'Save one screenshot per iteration that shows the largest contentful paint element (if the browser supports LCP).',
|
|
933
|
-
group: '
|
|
948
|
+
group: 'screenshot'
|
|
934
949
|
})
|
|
935
950
|
.option('screenshotLS', {
|
|
936
951
|
type: 'boolean',
|
|
937
952
|
default: false,
|
|
938
953
|
describe:
|
|
939
954
|
'Save one screenshot per iteration that shows the layout shift elements (if the browser supports layout shift).',
|
|
940
|
-
group: '
|
|
955
|
+
group: 'screenshot'
|
|
941
956
|
})
|
|
942
957
|
.option('screenshotParams.type', {
|
|
943
958
|
describe: 'Set the file type of the screenshot',
|
|
944
959
|
choices: ['png', 'jpg'],
|
|
945
960
|
default: screenshotDefaults.type,
|
|
946
|
-
group: '
|
|
961
|
+
group: 'screenshot'
|
|
947
962
|
})
|
|
948
963
|
.option('screenshotParams.jpg.quality', {
|
|
949
964
|
describe: 'Quality of the JPEG screenshot. 1-100',
|
|
950
965
|
default: screenshotDefaults.jpg.quality,
|
|
951
|
-
group: '
|
|
966
|
+
group: 'screenshot'
|
|
952
967
|
})
|
|
953
968
|
.option('screenshotParams.maxSize', {
|
|
954
969
|
describe: 'The max size of the screenshot (width and height).',
|
|
955
970
|
default: screenshotDefaults.maxSize,
|
|
956
|
-
group: '
|
|
971
|
+
group: 'screenshot'
|
|
957
972
|
})
|
|
958
973
|
.option('pageCompleteCheck', {
|
|
959
974
|
describe:
|
|
960
975
|
'Supply a JavaScript (inline or JavaScript file) that decides when the browser is finished loading the page and can start to collect metrics. The JavaScript snippet is repeatedly queried to see if page has completed loading (indicated by the script returning true). Use it to fetch timings happening after the loadEventEnd. By default the tests ends 2 seconds after loadEventEnd. Also checkout --pageCompleteCheckInactivity and --pageCompleteCheckPollTimeout',
|
|
961
|
-
group: '
|
|
976
|
+
group: 'pageload'
|
|
962
977
|
})
|
|
963
978
|
.option('pageCompleteWaitTime', {
|
|
964
979
|
describe:
|
|
965
980
|
'How long time you want to wait for your pageComplteteCheck to finish, after it is signaled to closed. Extra parameter passed on to your pageCompleteCheck.',
|
|
966
981
|
default: 8000,
|
|
967
|
-
group: '
|
|
982
|
+
group: 'pageload'
|
|
968
983
|
})
|
|
969
984
|
.option('pageCompleteCheckInactivity', {
|
|
970
985
|
describe:
|
|
971
986
|
'Alternative way to choose when to end your test. This will wait for 2 seconds of inactivity that happens after loadEventEnd.',
|
|
972
987
|
type: 'boolean',
|
|
973
988
|
default: false,
|
|
974
|
-
group: '
|
|
989
|
+
group: 'pageload'
|
|
975
990
|
})
|
|
976
991
|
.option('pageCompleteCheckNetworkIdle', {
|
|
977
992
|
describe:
|
|
978
993
|
'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 ',
|
|
979
994
|
type: 'boolean',
|
|
980
995
|
default: false,
|
|
981
|
-
group: '
|
|
996
|
+
group: 'pageload'
|
|
982
997
|
})
|
|
983
998
|
.option('pageCompleteCheckPollTimeout', {
|
|
984
999
|
type: 'number',
|
|
985
1000
|
default: 1500,
|
|
986
1001
|
describe:
|
|
987
1002
|
'The time in ms to wait for running the page complete check the next time.',
|
|
988
|
-
group: '
|
|
1003
|
+
group: 'pageload'
|
|
989
1004
|
})
|
|
990
1005
|
.option('pageCompleteCheckStartWait', {
|
|
991
1006
|
type: 'number',
|
|
992
1007
|
default: 5000,
|
|
993
1008
|
describe:
|
|
994
1009
|
'The time in ms to wait for running the page complete check for the first time. Use this when you have a pageLoadStrategy set to none',
|
|
995
|
-
group: '
|
|
1010
|
+
group: 'pageload'
|
|
996
1011
|
})
|
|
997
1012
|
.option('pageLoadStrategy', {
|
|
998
1013
|
type: 'string',
|
|
@@ -1000,7 +1015,7 @@ export function parseCommandLine() {
|
|
|
1000
1015
|
choices: ['eager', 'none', 'normal'],
|
|
1001
1016
|
describe:
|
|
1002
1017
|
'Set the strategy to waiting for document readiness after a navigation event. After the strategy is ready, your pageCompleteCheck will start running.',
|
|
1003
|
-
group: '
|
|
1018
|
+
group: 'pageload'
|
|
1004
1019
|
})
|
|
1005
1020
|
.option('iterations', {
|
|
1006
1021
|
alias: 'n',
|
|
@@ -1025,14 +1040,14 @@ export function parseCommandLine() {
|
|
|
1025
1040
|
default: 0,
|
|
1026
1041
|
describe:
|
|
1027
1042
|
'Extra time added for the browser to settle before starting to test a URL. This delay happens after the browser was opened and before the navigation to the URL',
|
|
1028
|
-
group: '
|
|
1043
|
+
group: 'pageload'
|
|
1029
1044
|
})
|
|
1030
1045
|
.option('webdriverPageload', {
|
|
1031
1046
|
type: 'boolean',
|
|
1032
1047
|
describe:
|
|
1033
1048
|
'Use webdriver.get to initialize the page load instead of window.location.',
|
|
1034
1049
|
default: false,
|
|
1035
|
-
group: '
|
|
1050
|
+
group: 'pageload'
|
|
1036
1051
|
})
|
|
1037
1052
|
.option('proxy.pac', {
|
|
1038
1053
|
type: 'string',
|
|
@@ -1168,7 +1183,7 @@ export function parseCommandLine() {
|
|
|
1168
1183
|
'Use internal browser functionality to clear browser cache between runs instead of only using Selenium.',
|
|
1169
1184
|
type: 'boolean',
|
|
1170
1185
|
default: false,
|
|
1171
|
-
group: '
|
|
1186
|
+
group: 'pageload'
|
|
1172
1187
|
})
|
|
1173
1188
|
.option('basicAuth', {
|
|
1174
1189
|
describe:
|
|
@@ -1310,7 +1325,7 @@ export function parseCommandLine() {
|
|
|
1310
1325
|
default: false,
|
|
1311
1326
|
describe:
|
|
1312
1327
|
'Flush DNS between runs, works on Mac OS and Linux. Your user needs sudo rights to be able to flush the DNS.',
|
|
1313
|
-
group: '
|
|
1328
|
+
group: 'pageload'
|
|
1314
1329
|
})
|
|
1315
1330
|
.option('extension', {
|
|
1316
1331
|
describe:
|
|
@@ -1321,7 +1336,7 @@ export function parseCommandLine() {
|
|
|
1321
1336
|
'Convenient parameter to use if you test a SPA application: will automatically wait for X seconds after last network activity and use hash in file names. Read more: https://www.sitespeed.io/documentation/sitespeed.io/spa/',
|
|
1322
1337
|
type: 'boolean',
|
|
1323
1338
|
default: false,
|
|
1324
|
-
group: '
|
|
1339
|
+
group: 'pageload'
|
|
1325
1340
|
})
|
|
1326
1341
|
.option('cjs', {
|
|
1327
1342
|
describe:
|
|
@@ -1334,7 +1349,7 @@ export function parseCommandLine() {
|
|
|
1334
1349
|
default: 3,
|
|
1335
1350
|
describe:
|
|
1336
1351
|
'If the browser fails to start, you can retry to start it this amount of times.',
|
|
1337
|
-
group: '
|
|
1352
|
+
group: 'pageload'
|
|
1338
1353
|
})
|
|
1339
1354
|
.option('preWarmServer', {
|
|
1340
1355
|
type: 'boolean',
|
|
@@ -1363,8 +1378,14 @@ export function parseCommandLine() {
|
|
|
1363
1378
|
.alias('V', 'version')
|
|
1364
1379
|
.alias('v', 'verbose')
|
|
1365
1380
|
.wrap(yargsInstance.terminalWidth())
|
|
1381
|
+
.epilog(buildEpilog(help.mode, help.topic, [...topicKeys.keys()]))
|
|
1366
1382
|
.check(validateInput);
|
|
1367
1383
|
|
|
1384
|
+
// Filter the visible options based on the requested help mode. This
|
|
1385
|
+
// must happen before .argv is read because that's what triggers
|
|
1386
|
+
// yargs to render help and exit.
|
|
1387
|
+
applyHelpFilter(yargsInstance, help.mode, help.topic, topicKeys);
|
|
1388
|
+
|
|
1368
1389
|
let argv = validated.argv;
|
|
1369
1390
|
|
|
1370
1391
|
if (
|
package/lib/support/har/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import merge from 'lodash.merge';
|
|
3
2
|
import { pathToFolder } from '../pathToFolder.js';
|
|
4
3
|
import { localTime } from '../util.js';
|
|
5
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
6
5
|
const log = getLogger('browsertime');
|
|
7
6
|
const require = createRequire(import.meta.url);
|
|
8
7
|
const version = require('../../../package.json').version;
|
|
9
|
-
import { pick, isEmpty, getProperty } from '../../support/util.js';
|
|
8
|
+
import { pick, isEmpty, getProperty, merge } from '../../support/util.js';
|
|
10
9
|
|
|
11
10
|
const sensitiveHeaders = new Set([
|
|
12
11
|
'authorization',
|