browsertime 25.4.0 → 26.0.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 +8 -1
- package/lib/firefox/settings/firefoxPreferences.js +3 -0
- package/lib/screenshot/loadCustomJimp.js +9 -13
- package/lib/support/cli.js +0 -5
- package/lib/support/images/index.js +7 -6
- package/lib/support/pathToFolder.js +70 -48
- package/lib/support/storageManager.js +31 -13
- package/package.json +2 -6
- package/types/support/filters.d.ts.map +1 -1
- package/types/support/pathToFolder.d.ts +1 -1
- package/types/support/pathToFolder.d.ts.map +1 -1
- package/types/support/storageManager.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
+
## 26.0.0 - 2025-12-10
|
|
4
|
+
### Breaking
|
|
5
|
+
* We removed support for setting the compression level for png screenshots, see the added section why.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
* Upgrade to support NodeJS 24 without warnings, include NodeJS 24 in the Docker container, and base the Docker container on Ubuntu 24.04. To make this work I needed to upgrade the Jimp library and then we lost the settings for png screenshots `--screenshotParams.png.compressionLevel` [#2342](https://github.com/sitespeedio/browsertime/pull/2342).
|
|
9
|
+
|
|
3
10
|
## 25.4.0 - 2025-11-02
|
|
4
11
|
### Added
|
|
5
12
|
* Chrome and Chromedriver 142 [#2335](https://github.com/sitespeedio/browsertime/pull/2335).
|
|
@@ -11,7 +18,7 @@
|
|
|
11
18
|
|
|
12
19
|
## 25.3.0 - 2025-10-17
|
|
13
20
|
### Added
|
|
14
|
-
* Make it possible to strip cookie and auth headers in the HAR file for Firefox [#2329](https://github.com/sitespeedio/browsertime/pull/2329) and Chrome [#2330](https://github.com/sitespeedio/browsertime/pull/2330). Use `--cleanSensitiveHeaders` to remove a [couple of headers](https://github.com/sitespeedio/browsertime/blob/main/lib/support/har/index.js#L11-L24).
|
|
21
|
+
* Make it possible to strip cookie and auth headers in the HAR file for Firefox [#2329](https://github.com/sitespeedio/browsertime/pull/2329) and Chrome [#2330](https://github.com/sitespeedio/browsertime/pull/2330). Use `--cleanSensitiveHeaders` to remove a [couple of headers](https://github.com/sitespeedio/browsertime/blob/main/lib/support/har/index.js#L11-L24).
|
|
15
22
|
* Firefox 144 [#2331](https://github.com/sitespeedio/browsertime/pull/2331).
|
|
16
23
|
|
|
17
24
|
### Fixed
|
|
@@ -43,6 +43,9 @@ export const defaultFirefoxPreferences = {
|
|
|
43
43
|
// Prevent network access for recommendations by default. The payload is {'results':[]}.
|
|
44
44
|
'extensions.getAddons.discovery.api_url':
|
|
45
45
|
'data:;base64,eyJyZXN1bHRzIjpbXX0%3D',
|
|
46
|
+
// Enable form autofill
|
|
47
|
+
'extensions.formautofill.addresses.enabled': true,
|
|
48
|
+
'extensions.formautofill.creditCards.enabled': true,
|
|
46
49
|
// Treat WebExtension API/schema warnings as errors.
|
|
47
50
|
'extensions.webextensions.warnings-as-errors': true,
|
|
48
51
|
// Disable useragent updates.
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
export async function loadCustomJimp() {
|
|
2
2
|
try {
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
});
|
|
13
|
-
return jimp;
|
|
14
|
-
} catch {
|
|
15
|
-
return;
|
|
3
|
+
const { Jimp } = await import('jimp');
|
|
4
|
+
|
|
5
|
+
return Jimp;
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error?.code === 'ERR_MODULE_NOT_FOUND') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
throw error;
|
|
16
12
|
}
|
|
17
13
|
}
|
package/lib/support/cli.js
CHANGED
|
@@ -932,11 +932,6 @@ export function parseCommandLine() {
|
|
|
932
932
|
default: screenshotDefaults.type,
|
|
933
933
|
group: 'Screenshot'
|
|
934
934
|
})
|
|
935
|
-
.option('screenshotParams.png.compressionLevel', {
|
|
936
|
-
describe: 'zlib compression level',
|
|
937
|
-
default: screenshotDefaults.png.compressionLevel,
|
|
938
|
-
group: 'Screenshot'
|
|
939
|
-
})
|
|
940
935
|
.option('screenshotParams.jpg.quality', {
|
|
941
936
|
describe: 'Quality of the JPEG screenshot. 1-100',
|
|
942
937
|
default: screenshotDefaults.jpg.quality,
|
|
@@ -32,9 +32,9 @@ export async function savePng(
|
|
|
32
32
|
if (jimp) {
|
|
33
33
|
const image = await jimp.read(data);
|
|
34
34
|
const buffer = await image
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
|
|
35
|
+
.scaleToFit({ w: config.maxSize, h: config.maxSize })
|
|
36
|
+
.getBuffer('image/png');
|
|
37
|
+
console.log('3');
|
|
38
38
|
|
|
39
39
|
return storageManager.writeData(
|
|
40
40
|
`${name}.png`,
|
|
@@ -61,11 +61,12 @@ export async function saveJpg(
|
|
|
61
61
|
|
|
62
62
|
if (jimp) {
|
|
63
63
|
const image = await jimp.read(data);
|
|
64
|
+
|
|
64
65
|
// https://github.com/sitespeedio/sitespeed.io/issues/3922
|
|
65
66
|
const buffer = await image
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
|
|
67
|
+
.scaleToFit({ w: config.maxSize, h: config.maxSize })
|
|
68
|
+
.getBuffer('image/jpeg', { quality: config.jpg.quality });
|
|
69
|
+
|
|
69
70
|
return storageManager.writeData(
|
|
70
71
|
`${name}.jpg`,
|
|
71
72
|
buffer,
|
|
@@ -1,68 +1,90 @@
|
|
|
1
|
-
import { parse } from 'node:url';
|
|
2
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { getLogger } from '@sitespeed.io/log';
|
|
4
4
|
import { isEmpty } from './util.js';
|
|
5
|
+
|
|
5
6
|
const log = getLogger('browsertime');
|
|
6
7
|
|
|
8
|
+
function isHttpLikeUrl(s) {
|
|
9
|
+
if (typeof s !== 'string' || s.length === 0) return false;
|
|
10
|
+
if (s.startsWith('//')) return true;
|
|
11
|
+
return /^https?:\/\//iu.test(s);
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
function toSafeKey(key) {
|
|
8
|
-
|
|
9
|
-
return key.replaceAll(/[ %&()+,./:?|~–]|%7C/g, '-');
|
|
15
|
+
return key.replaceAll(/[ %&()+,./:?|~–]|%7C/gu, '-');
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} else {
|
|
16
|
-
const useHash = options.useHash;
|
|
17
|
-
const parsedUrl = parse(decodeURIComponent(url));
|
|
18
|
+
function md5Hex8(s) {
|
|
19
|
+
return createHash('md5').update(s).digest('hex').slice(0, 8);
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
function normalizeFsPath(input) {
|
|
23
|
+
let n = path.normalize(input);
|
|
24
|
+
if (n.startsWith(`.${path.sep}`)) n = n.slice(2);
|
|
25
|
+
return n;
|
|
26
|
+
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} else {
|
|
26
|
-
if (!isEmpty(parsedUrl.pathname)) {
|
|
27
|
-
urlSegments.push(...parsedUrl.pathname.split('/').filter(Boolean));
|
|
28
|
-
}
|
|
28
|
+
export function pathToFolder(input, options) {
|
|
29
|
+
if (options.useSameDir) return '';
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
31
|
+
let hostname = '';
|
|
32
|
+
let pathname = '';
|
|
33
|
+
let search = '';
|
|
34
|
+
let hash = '';
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
const md5 = createHash('md5'),
|
|
38
|
-
hash = md5.update(parsedUrl.search).digest('hex').slice(0, 8);
|
|
39
|
-
urlSegments.push('query-' + hash);
|
|
40
|
-
}
|
|
36
|
+
const isUrl = isHttpLikeUrl(input);
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
if (isUrl) {
|
|
39
|
+
const raw = input.startsWith('//') ? `http:${input}` : input;
|
|
40
|
+
const u = new URL(raw);
|
|
41
|
+
hostname = u.hostname;
|
|
42
|
+
pathname = u.pathname; // '/'-separated
|
|
43
|
+
search = u.search; // includes '?'
|
|
44
|
+
hash = u.hash; // includes '#'
|
|
45
|
+
} else {
|
|
46
|
+
hostname = 'file';
|
|
47
|
+
const fsNormalized = normalizeFsPath(input);
|
|
48
|
+
pathname = `${path.sep}${fsNormalized}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pathSegments = ['pages', hostname.split('.').join('_')];
|
|
52
|
+
const urlSegments = [];
|
|
53
|
+
|
|
54
|
+
if (options.urlMetaData && options.urlMetaData[input]) {
|
|
55
|
+
pathSegments.push(options.urlMetaData[input]);
|
|
56
|
+
} else {
|
|
57
|
+
const parts = isUrl
|
|
58
|
+
? pathname.split('/').filter(Boolean)
|
|
59
|
+
: pathname.split(/[\\/]/u).filter(Boolean);
|
|
60
|
+
if (!isEmpty(parts)) urlSegments.push(...parts);
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
if (isUrl) {
|
|
63
|
+
if (options.useHash && !isEmpty(hash))
|
|
64
|
+
urlSegments.push(`hash-${md5Hex8(hash)}`);
|
|
65
|
+
if (!isEmpty(search)) urlSegments.push(`query-${md5Hex8(search)}`);
|
|
66
|
+
}
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
if (options.storeURLsAsFlatPageOnDisk) {
|
|
69
|
+
const folder = toSafeKey(`${urlSegments.join('_')}_`);
|
|
70
|
+
if (folder.length > 255) {
|
|
71
|
+
log.info(
|
|
72
|
+
`The URL ${input} hit the 255 character limit used when stored on disk, you may want to give your URL an alias to make sure it will not collide with other URLs.`
|
|
73
|
+
);
|
|
74
|
+
pathSegments.push(folder.slice(0, 254));
|
|
75
|
+
} else {
|
|
76
|
+
pathSegments.push(folder);
|
|
63
77
|
}
|
|
78
|
+
} else {
|
|
79
|
+
pathSegments.push(...urlSegments);
|
|
64
80
|
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pathSegments.push('data');
|
|
65
84
|
|
|
66
|
-
|
|
85
|
+
for (const [i, seg] of pathSegments.entries()) {
|
|
86
|
+
if (seg) pathSegments[i] = seg.replaceAll(/[^\w.\u0621-\u064A-]/giu, '-');
|
|
67
87
|
}
|
|
88
|
+
|
|
89
|
+
return `${path.join(...pathSegments)}${path.sep}`;
|
|
68
90
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
// storage-manager.js
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { gunzip as _gunzip, gzip as _gzip, createGzip } from 'node:zlib';
|
|
4
|
-
import {
|
|
5
|
+
import { URL } from 'node:url';
|
|
5
6
|
import { promisify } from 'node:util';
|
|
6
7
|
import {
|
|
7
8
|
writeFile as _writeFile,
|
|
@@ -25,16 +26,35 @@ const log = getLogger('browsertime');
|
|
|
25
26
|
const defaultDir = 'browsertime-results';
|
|
26
27
|
let timestamp = localTime().replaceAll(':', '');
|
|
27
28
|
|
|
28
|
-
function pathNameFromUrl(
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
function pathNameFromUrl(input) {
|
|
30
|
+
// If it's a proper web URL → use WHATWG URL.
|
|
31
|
+
// Otherwise treat it as a filesystem path and return only the basename.
|
|
32
|
+
let asUrl;
|
|
33
|
+
try {
|
|
34
|
+
asUrl = new URL(input); // succeeds only for absolute URLs like https://..., file://..., etc.
|
|
35
|
+
} catch {
|
|
36
|
+
// Filesystem path case (e.g. "test.js", "my/path/to/test.js")
|
|
37
|
+
// We want exactly "test.js" (no hostname, no parent dirs).
|
|
38
|
+
let base = path.basename(path.normalize(input));
|
|
39
|
+
// If someone passed a trailing slash directory, basename returns '' → keep it stable.
|
|
40
|
+
if (!base)
|
|
41
|
+
base = input.replaceAll('\\', '/').split('/').findLast(Boolean) ?? '';
|
|
42
|
+
return base;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// URL case: mirror your previous behavior
|
|
46
|
+
const decodedPathname = decodeURIComponent(asUrl.pathname);
|
|
47
|
+
const pathSegments = decodedPathname.split('/');
|
|
31
48
|
|
|
32
|
-
|
|
49
|
+
// Only add hostname for real web URLs (file: URLs typically have empty hostname on POSIX)
|
|
50
|
+
if (asUrl.hostname) {
|
|
51
|
+
pathSegments.unshift(asUrl.hostname);
|
|
52
|
+
}
|
|
33
53
|
|
|
34
|
-
if (!isEmpty(
|
|
35
|
-
const md5 = createHash('md5')
|
|
36
|
-
|
|
37
|
-
pathSegments.push(
|
|
54
|
+
if (!isEmpty(asUrl.search)) {
|
|
55
|
+
const md5 = createHash('md5');
|
|
56
|
+
const hash = md5.update(asUrl.search).digest('hex').slice(0, 8);
|
|
57
|
+
pathSegments.push(`query-${hash}`);
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
return pathSegments.filter(Boolean).join('-');
|
|
@@ -73,8 +93,7 @@ export class StorageManager {
|
|
|
73
93
|
}
|
|
74
94
|
|
|
75
95
|
async writeData(filename, data, subdir) {
|
|
76
|
-
|
|
77
|
-
dirPath = await (subdir
|
|
96
|
+
const dirPath = await (subdir
|
|
78
97
|
? this.createSubDataDir(subdir)
|
|
79
98
|
: this.createDataDir());
|
|
80
99
|
const fullPath = path.join(dirPath, filename);
|
|
@@ -94,8 +113,7 @@ export class StorageManager {
|
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
async readData(filename, subdir) {
|
|
97
|
-
|
|
98
|
-
filepath = subdir
|
|
116
|
+
const filepath = subdir
|
|
99
117
|
? path.join(this.baseDir, subdir, filename)
|
|
100
118
|
: path.join(this.baseDir, filename);
|
|
101
119
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browsertime",
|
|
3
3
|
"description": "Get performance metrics from your web page using Browsertime.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "26.0.0",
|
|
5
5
|
"bin": "./bin/browsertime.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./types/scripting.d.ts",
|
|
@@ -23,11 +23,7 @@
|
|
|
23
23
|
"yargs": "18.0.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"@jimp/png": "0.22.12",
|
|
28
|
-
"@jimp/jpeg": "0.22.12",
|
|
29
|
-
"@jimp/plugin-resize": "0.22.12",
|
|
30
|
-
"@jimp/plugin-scale": "0.22.12",
|
|
26
|
+
"jimp": "1.6.0",
|
|
31
27
|
"usb-power-profiling": "1.6.0"
|
|
32
28
|
},
|
|
33
29
|
"devDependencies": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../lib/support/filters.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,
|
|
1
|
+
{"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../lib/support/filters.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,oDACS,aAAQ,aAChB;AACD,2DAGC;AACD,iEAGC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export function pathToFolder(
|
|
1
|
+
export function pathToFolder(input: any, options: any): string;
|
|
2
2
|
//# sourceMappingURL=pathToFolder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathToFolder.d.ts","sourceRoot":"","sources":["../../lib/support/pathToFolder.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pathToFolder.d.ts","sourceRoot":"","sources":["../../lib/support/pathToFolder.js"],"names":[],"mappings":"AA2BA,+DA8DC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../lib/support/storageManager.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../lib/support/storageManager.js"],"names":[],"mappings":"AAuEA;IACE;;OAKC;IAJC,gBAE6D;IAC7D,wBAA0C;IAG5C,iCAGC;IAED,kDAIC;IAED,iCAEC;IAED,kEAQC;IAED,sEAQC;IAED,+DAaC;IAED,sEAmBC;IAED,wBAEC;CACF"}
|