sitespeed.io 36.4.1 → 37.0.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 +21 -0
- package/HELP.md +2 -9
- package/lib/cli/cli.js +28 -97
- package/lib/cli/pluginOptions.js +34 -0
- package/lib/core/logging.js +1 -0
- package/lib/core/pluginLoader.js +12 -6
- package/lib/plugins/browsertime/index.js +4 -2
- package/lib/plugins/budget/junit.js +77 -51
- package/lib/plugins/budget/tap.js +28 -20
- package/lib/plugins/grafana/send-annotation.js +1 -3
- package/lib/plugins/pagexray/index.js +11 -9
- package/lib/plugins/pagexray/pagexrayAggregator.js +2 -1
- package/npm-shrinkwrap.json +20 -1139
- package/package.json +2 -6
- package/lib/plugins/influxdb/data-generator.js +0 -262
- package/lib/plugins/influxdb/index.js +0 -176
- package/lib/plugins/influxdb/send-annotation.js +0 -111
- package/lib/plugins/influxdb/send-annotationV2.js +0 -106
- package/lib/plugins/influxdb/sender.js +0 -26
- package/lib/plugins/influxdb/senderV2.js +0 -38
- package/tools/influxdb/docker-compose.yml +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
|
|
2
2
|
# CHANGELOG - sitespeed.io (we use [semantic versioning](https://semver.org))
|
|
3
3
|
|
|
4
|
+
## 37.0.1 - 2025-03-05
|
|
5
|
+
### Fixed
|
|
6
|
+
* There was a bug in the InfluxDB plugin and loading the cli parameters failed [#4463](https://github.com/sitespeedio/sitespeed.io/pull/4463).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## 37.0.0 - 2025-03-05
|
|
10
|
+
### Breaking change
|
|
11
|
+
* If you are a InfluxDB user the InfluxDB functionality been moved to a standalone plugin [plugin-influxdb](https://github.com/sitespeedio/plugin-influxdb). This means if that sitespeed.io using NodeJS and the default Docker container will not include the InfluxDB plugin. The +1 container will still include the plugin but you need to add it with `--plugins.add @sitespeed.io/plugin-influxdb` for it to be able to run.
|
|
12
|
+
|
|
13
|
+
The InfluxDB plugin has never gotten the love it deserves, moving it out, it means that you easier can do your own modification and get the data the way that you need.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
* Support for getting CLI options from plugins as long as you use `--help --plugins.add`. This is start to separate CLI options from the monsterous CLI file and instead have them in each plugin [#4450](https://github.com/sitespeedio/sitespeed.io/pull/4450), [#4452](https://github.com/sitespeedio/sitespeed.io/pull/4452) [#4455](https://github.com/sitespeedio/sitespeed.io/pull/4455).
|
|
17
|
+
* You can now set the exact minimum log level using `--logLevel`[#4459](https://github.com/sitespeedio/sitespeed.io/pull/4459).
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
* Replace the junit-report-builder package [#4448](https://github.com/sitespeedio/sitespeed.io/pull/4448).
|
|
21
|
+
* Remove Tape dependencies [#4447](https://github.com/sitespeedio/sitespeed.io/pull/4447).
|
|
22
|
+
* Catch if the browser fails to open a broken page [#4457](https://github.com/sitespeedio/sitespeed.io/pull/4457).
|
|
23
|
+
|
|
24
|
+
|
|
4
25
|
## 36.4.1 - 2025-02-17
|
|
5
26
|
### Fixed
|
|
6
27
|
* The Docker container for last release was never push. The reason is that our GitHub action that do the release automatically was upgraded to Ubuntu 24 and there its a problem building containers for ARM. With this release we use Ubuntu 22 instead.
|
package/HELP.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
We want to make sitespeed.io one of the best web performance tool in the world and we hope you can help us!
|
|
3
3
|
|
|
4
4
|
## Developers
|
|
5
|
-
We love to have more people involved in improving sitespeed.io. We are constantly working on adding more documentation and trying to write more information in the issues so its easier to help out. If there's an [issue](https://github.com/sitespeedio/sitespeed.io/issues) that you want to take on, ping the the issue and we can help you get started. You can also [join our Slack channel](https://join.slack.com/t/sitespeedio/shared_invite/zt-296jzr7qs-d6DId2KpEnMPJSQ8_R~WFw) if you need help!
|
|
5
|
+
We love to have more people involved in improving sitespeed.io. We are constantly working on adding more documentation and trying to write more information in the issues so its easier to help out. If there's an [issue](https://github.com/sitespeedio/sitespeed.io/issues) that you want to take on, ping the the issue and we can help you get started. You can also [join our Slack channel](https://join.slack.com/t/sitespeedio/shared_invite/zt-296jzr7qs-d6DId2KpEnMPJSQ8_R~WFw) if you need help! You can start by reading the [developer documentation](https://www.sitespeed.io/documentation/sitespeed.io/developers/).
|
|
6
6
|
|
|
7
7
|
## Designers
|
|
8
8
|
As a designer there's a lot you can do: You can help us improve the HTML result pages. Maybe we should restructure the metrics ? Or could the header/footer look better? You could also have look at [https://www.sitespeed.io](https://www.sitespeed.io/) where we have all the documentation. You can pretty much help us with everything, no one in the core team got design skills :)
|
|
@@ -10,13 +10,6 @@ As a designer there's a lot you can do: You can help us improve the HTML result
|
|
|
10
10
|
## Documentation
|
|
11
11
|
Documentation is fun and it is the core of making sitespeed.io easy to use. We got a [special documentation tag for issues](https://github.com/sitespeedio/sitespeed.io/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) that you can use to find where we know we lack documentation. Fixing spelling mistakes is great. Or rewrite parts that you think is too complicated. You can find what you need to send a PR to the documentation [here](https://github.com/sitespeedio/sitespeed.io/tree/main/docs).
|
|
12
12
|
|
|
13
|
-
## Tests
|
|
14
|
-
We lack unit tests. You can read about [our testing pipeline](https://www.sitespeed.io/releasing-with-confidence/) that works good for us but more unit tests are always good. A good start is adding support for the code in the [support library](https://github.com/sitespeedio/sitespeed.io/tree/main/lib/support).
|
|
15
|
-
|
|
16
13
|
|
|
17
14
|
## Companies
|
|
18
|
-
Do you use sitespeed.io in your everyday work? Then
|
|
19
|
-
|
|
20
|
-
# Help wanted
|
|
21
|
-
You can help us:
|
|
22
|
-
* Help us improve the documentation. We love your feedback and help [https://github.com/sitespeedio/sitespeed.io/tree/main/docs](https://github.com/sitespeedio/sitespeed.io/tree/main/docs).
|
|
15
|
+
Do you use sitespeed.io in your everyday work? Then go to the [sponsor page](https://www.sitespeed.io/sponsor/).
|
package/lib/cli/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { config as slackConfig } from '../plugins/slack/index.js';
|
|
|
18
18
|
import { config as htmlConfig } from '../plugins/html/index.js';
|
|
19
19
|
import { messageTypes as matrixMessageTypes } from '../plugins/matrix/index.js';
|
|
20
20
|
import { findUpSync } from '../support/fileUtil.js';
|
|
21
|
+
import { registerPluginOptions } from './pluginOptions.js';
|
|
21
22
|
|
|
22
23
|
const metricList = Object.keys(friendlynames);
|
|
23
24
|
const require = createRequire(import.meta.url);
|
|
@@ -25,6 +26,13 @@ const version = require('../../package.json').version;
|
|
|
25
26
|
|
|
26
27
|
const configFiles = ['.sitespeed.io.json'];
|
|
27
28
|
|
|
29
|
+
const addedPlugins = yargs(hideBin(process.argv))
|
|
30
|
+
.option('plugins.add', { type: 'array' })
|
|
31
|
+
.help(false)
|
|
32
|
+
.version(false)
|
|
33
|
+
.parse();
|
|
34
|
+
const globalPluginsToAdd = addedPlugins.plugins?.add || [];
|
|
35
|
+
|
|
28
36
|
function fixAndroidArgs(args) {
|
|
29
37
|
return args.map(arg => (arg === '--android' ? '--android.enabled' : arg));
|
|
30
38
|
}
|
|
@@ -220,7 +228,7 @@ function validateInput(argv) {
|
|
|
220
228
|
export async function parseCommandLine() {
|
|
221
229
|
const fixedArgs = fixAndroidArgs(hideBin(process.argv));
|
|
222
230
|
const yargsInstance = yargs(fixedArgs);
|
|
223
|
-
|
|
231
|
+
yargsInstance
|
|
224
232
|
.parserConfiguration({
|
|
225
233
|
'camel-case-expansion': false,
|
|
226
234
|
'deep-merge-config': true
|
|
@@ -421,7 +429,7 @@ export async function parseCommandLine() {
|
|
|
421
429
|
})
|
|
422
430
|
.option('browsertime.script', {
|
|
423
431
|
describe:
|
|
424
|
-
'Add custom Javascript that collect metrics and run after the page has finished loading. Note that --script can be passed multiple times if you want to collect multiple metrics. The metrics will automatically be pushed to the summary/detailed summary and each individual page + sent to Graphite
|
|
432
|
+
'Add custom Javascript that collect metrics and run after the page has finished loading. Note that --script can be passed multiple times if you want to collect multiple metrics. The metrics will automatically be pushed to the summary/detailed summary and each individual page + sent to Graphite',
|
|
425
433
|
alias: ['script'],
|
|
426
434
|
group: 'Browser'
|
|
427
435
|
})
|
|
@@ -513,7 +521,7 @@ export async function parseCommandLine() {
|
|
|
513
521
|
.option('browsertime.scriptInput.visualElements', {
|
|
514
522
|
alias: ['scriptInput.visualElements'],
|
|
515
523
|
describe:
|
|
516
|
-
'Include specific elements in visual elements. Give the element a name and select it with document.body.querySelector. Use like this: --scriptInput.visualElements name:domSelector .
|
|
524
|
+
'Include specific elements in visual elements. Give the element a name and select it with document.body.querySelector. Use like this: --scriptInput.visualElements name:domSelector . If you want to measure multiple elements, use a configuration file with an array for the input. Visual Metrics will use these elements and calculate when they are visible and fully rendered.',
|
|
517
525
|
group: 'Browser'
|
|
518
526
|
})
|
|
519
527
|
.option('browsertime.scriptInput.longTask', {
|
|
@@ -1160,7 +1168,6 @@ export async function parseCommandLine() {
|
|
|
1160
1168
|
describe: 'Ignore robots.txt rules of the crawled domain.',
|
|
1161
1169
|
group: 'Crawler'
|
|
1162
1170
|
})
|
|
1163
|
-
|
|
1164
1171
|
.option('scp.host', {
|
|
1165
1172
|
describe: 'The host.',
|
|
1166
1173
|
group: 'scp'
|
|
@@ -1347,80 +1354,6 @@ export async function parseCommandLine() {
|
|
|
1347
1354
|
'Define which messages to send to Graphite. By default we do not send data per run, but you can change that by adding run as one of the options',
|
|
1348
1355
|
group: 'Graphite'
|
|
1349
1356
|
})
|
|
1350
|
-
|
|
1351
|
-
.option('influxdb.protocol', {
|
|
1352
|
-
describe: 'The protocol used to store connect to the InfluxDB host.',
|
|
1353
|
-
default: 'http',
|
|
1354
|
-
group: 'InfluxDB'
|
|
1355
|
-
})
|
|
1356
|
-
.option('influxdb.host', {
|
|
1357
|
-
describe: 'The InfluxDB host used to store captured metrics.',
|
|
1358
|
-
group: 'InfluxDB'
|
|
1359
|
-
})
|
|
1360
|
-
.option('influxdb.port', {
|
|
1361
|
-
default: 8086,
|
|
1362
|
-
describe: 'The InfluxDB port used to store captured metrics.',
|
|
1363
|
-
group: 'InfluxDB'
|
|
1364
|
-
})
|
|
1365
|
-
.option('influxdb.username', {
|
|
1366
|
-
describe:
|
|
1367
|
-
'The InfluxDB username for your InfluxDB instance (only for InfluxDB v1)',
|
|
1368
|
-
group: 'InfluxDB'
|
|
1369
|
-
})
|
|
1370
|
-
.option('influxdb.password', {
|
|
1371
|
-
describe:
|
|
1372
|
-
'The InfluxDB password for your InfluxDB instance (only for InfluxDB v1).',
|
|
1373
|
-
group: 'InfluxDB'
|
|
1374
|
-
})
|
|
1375
|
-
.option('influxdb.organisation', {
|
|
1376
|
-
describe:
|
|
1377
|
-
'The InfluxDB organisation for your InfluxDB instance (only for InfluxDB v2)',
|
|
1378
|
-
group: 'InfluxDB'
|
|
1379
|
-
})
|
|
1380
|
-
.option('influxdb.token', {
|
|
1381
|
-
describe:
|
|
1382
|
-
'The InfluxDB token for your InfluxDB instance (only for InfluxDB v2)',
|
|
1383
|
-
group: 'InfluxDB'
|
|
1384
|
-
})
|
|
1385
|
-
.option('influxdb.version', {
|
|
1386
|
-
default: 1,
|
|
1387
|
-
describe: 'The InfluxDB version of your InfluxDB instance.',
|
|
1388
|
-
type: 'integer',
|
|
1389
|
-
group: 'InfluxDB'
|
|
1390
|
-
})
|
|
1391
|
-
.option('influxdb.database', {
|
|
1392
|
-
default: 'sitespeed',
|
|
1393
|
-
describe: 'The database name used to store captured metrics.',
|
|
1394
|
-
group: 'InfluxDB'
|
|
1395
|
-
})
|
|
1396
|
-
.option('influxdb.tags', {
|
|
1397
|
-
default: 'category=default',
|
|
1398
|
-
describe:
|
|
1399
|
-
'A comma separated list of tags and values added to each metric',
|
|
1400
|
-
group: 'InfluxDB'
|
|
1401
|
-
})
|
|
1402
|
-
.option('influxdb.includeQueryParams', {
|
|
1403
|
-
default: false,
|
|
1404
|
-
describe:
|
|
1405
|
-
'Whether to include query parameters from the URL in the InfluxDB keys or not',
|
|
1406
|
-
type: 'boolean',
|
|
1407
|
-
group: 'InfluxDB'
|
|
1408
|
-
})
|
|
1409
|
-
.option('influxdb.groupSeparator', {
|
|
1410
|
-
default: '_',
|
|
1411
|
-
describe:
|
|
1412
|
-
'Choose which character that will separate a group/domain. Default is underscore, set it to a dot if you wanna keep the original domain name.',
|
|
1413
|
-
group: 'InfluxDB'
|
|
1414
|
-
})
|
|
1415
|
-
.option('influxdb.annotationScreenshot', {
|
|
1416
|
-
default: false,
|
|
1417
|
-
type: 'boolean',
|
|
1418
|
-
describe:
|
|
1419
|
-
'Include screenshot (from Browsertime) in the annotation. You need to specify a --resultBaseURL for this to work.',
|
|
1420
|
-
group: 'InfluxDB'
|
|
1421
|
-
});
|
|
1422
|
-
|
|
1423
|
-
parsed
|
|
1424
1357
|
/** Plugins */
|
|
1425
1358
|
.option('plugins.list', {
|
|
1426
1359
|
describe: 'List all configured plugins in the log.',
|
|
@@ -1526,13 +1459,7 @@ export async function parseCommandLine() {
|
|
|
1526
1459
|
describe: 'The max size of the screenshot (width and height).',
|
|
1527
1460
|
default: browsertimeConfig.screenshotParams.maxSize,
|
|
1528
1461
|
group: 'Screenshot'
|
|
1529
|
-
})
|
|
1530
|
-
/**
|
|
1531
|
-
InfluxDB cli option
|
|
1532
|
-
*/
|
|
1533
|
-
|
|
1534
|
-
parsed
|
|
1535
|
-
// Metrics
|
|
1462
|
+
})
|
|
1536
1463
|
.option('metrics.list', {
|
|
1537
1464
|
describe: 'List all possible metrics in the data folder (metrics.txt).',
|
|
1538
1465
|
type: 'boolean',
|
|
@@ -1877,9 +1804,7 @@ export async function parseCommandLine() {
|
|
|
1877
1804
|
describe:
|
|
1878
1805
|
'Instead of using the local copy of the hosting database, you can use the latest version through the Green Web Foundation API. This means sitespeed.io will make HTTP GET to the the hosting info.',
|
|
1879
1806
|
group: 'Sustainable'
|
|
1880
|
-
})
|
|
1881
|
-
|
|
1882
|
-
parsed
|
|
1807
|
+
})
|
|
1883
1808
|
.option('api.key', {
|
|
1884
1809
|
describe: 'The API key to use.',
|
|
1885
1810
|
group: 'API'
|
|
@@ -1930,9 +1855,7 @@ export async function parseCommandLine() {
|
|
|
1930
1855
|
.option('api.json', {
|
|
1931
1856
|
describe: 'Output the result as JSON.',
|
|
1932
1857
|
group: 'API'
|
|
1933
|
-
})
|
|
1934
|
-
|
|
1935
|
-
parsed
|
|
1858
|
+
})
|
|
1936
1859
|
.option('compare.id', {
|
|
1937
1860
|
type: 'string',
|
|
1938
1861
|
describe:
|
|
@@ -1993,8 +1916,7 @@ export async function parseCommandLine() {
|
|
|
1993
1916
|
'Selects the method for calculating the Mann-Whitney U test. auto automatically selects between exact and asymptotic based on sample size, exact uses the exact distribution of U, and symptotic uses a normal approximation.',
|
|
1994
1917
|
default: 'auto',
|
|
1995
1918
|
group: 'compare'
|
|
1996
|
-
})
|
|
1997
|
-
parsed
|
|
1919
|
+
})
|
|
1998
1920
|
.option('mobile', {
|
|
1999
1921
|
describe:
|
|
2000
1922
|
'Access pages as mobile a fake mobile device. Set UA and width/height. For Chrome it will use device Moto G4.',
|
|
@@ -2028,12 +1950,12 @@ export async function parseCommandLine() {
|
|
|
2028
1950
|
})
|
|
2029
1951
|
.option('urlAlias', {
|
|
2030
1952
|
describe:
|
|
2031
|
-
'Use an alias for the URL (if you feed URLs from a file you can instead have the alias in the file). You need to pass on the same amount of alias as URLs. The alias is used as the name of the URL on the HTML report and in Graphite
|
|
1953
|
+
'Use an alias for the URL (if you feed URLs from a file you can instead have the alias in the file). You need to pass on the same amount of alias as URLs. The alias is used as the name of the URL on the HTML report and in Graphite. Pass on multiple --urlAlias for multiple alias/URLs. This will override alias in a file.',
|
|
2032
1954
|
type: 'string'
|
|
2033
1955
|
})
|
|
2034
1956
|
.option('groupAlias', {
|
|
2035
1957
|
describe:
|
|
2036
|
-
'Use an alias for the group/domain. You need to pass on the same amount of alias as URLs. The alias is used as the name of the group in Graphite
|
|
1958
|
+
'Use an alias for the group/domain. You need to pass on the same amount of alias as URLs. The alias is used as the name of the group in Graphite. Pass on multiple --groupAlias for multiple alias/groups. This do not work for scripting at the moment.',
|
|
2037
1959
|
type: 'string'
|
|
2038
1960
|
})
|
|
2039
1961
|
.option('utc', {
|
|
@@ -2056,6 +1978,11 @@ export async function parseCommandLine() {
|
|
|
2056
1978
|
.option('name', {
|
|
2057
1979
|
describe: 'Give your test a name.'
|
|
2058
1980
|
})
|
|
1981
|
+
.option('logLevel', {
|
|
1982
|
+
type: 'string',
|
|
1983
|
+
choices: ['trace', 'verbose', 'debug', 'info', 'warning', 'error'],
|
|
1984
|
+
describe: 'Manually set the min log level'
|
|
1985
|
+
})
|
|
2059
1986
|
.option('open', {
|
|
2060
1987
|
alias: ['o', 'view'],
|
|
2061
1988
|
describe:
|
|
@@ -2124,8 +2051,12 @@ export async function parseCommandLine() {
|
|
|
2124
2051
|
}
|
|
2125
2052
|
return plugins;
|
|
2126
2053
|
}
|
|
2127
|
-
})
|
|
2128
|
-
|
|
2054
|
+
});
|
|
2055
|
+
// .describe('browser', 'Specify browser')
|
|
2056
|
+
|
|
2057
|
+
await registerPluginOptions(yargsInstance, globalPluginsToAdd);
|
|
2058
|
+
|
|
2059
|
+
let parsed = yargsInstance
|
|
2129
2060
|
.wrap(yargsInstance.terminalWidth())
|
|
2130
2061
|
// .check(validateInput)
|
|
2131
2062
|
.epilog(
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { importGlobalSilent } from 'import-global';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamically load and register CLI options from plugins.
|
|
5
|
+
*
|
|
6
|
+
* @param {import('yargs').Argv} yargsInstance - The yargs instance to extend.
|
|
7
|
+
* @param {string[]} plugins - Array of plugin module names.
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
*/
|
|
10
|
+
export async function registerPluginOptions(yargsInstance, plugins) {
|
|
11
|
+
for (const pluginName of plugins) {
|
|
12
|
+
try {
|
|
13
|
+
// Dynamically import the plugin
|
|
14
|
+
let { default: plugin } = await importGlobalSilent(pluginName);
|
|
15
|
+
// If the plugin exports a function to get CLI options, merge them
|
|
16
|
+
if (plugin && typeof plugin.getCliOptions === 'function') {
|
|
17
|
+
const options = plugin.getCliOptions();
|
|
18
|
+
yargsInstance.options(options);
|
|
19
|
+
} else {
|
|
20
|
+
try {
|
|
21
|
+
const { default: plugin } = await import(pluginName);
|
|
22
|
+
if (plugin && typeof plugin.getCliOptions === 'function') {
|
|
23
|
+
const options = plugin.getCliOptions();
|
|
24
|
+
yargsInstance.options(options);
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Swallow this silent
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Swallow this silent
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/lib/core/logging.js
CHANGED
package/lib/core/pluginLoader.js
CHANGED
|
@@ -81,12 +81,18 @@ export async function loadPlugins(pluginNames, options, context, queue) {
|
|
|
81
81
|
plugins.push(p);
|
|
82
82
|
} catch (error) {
|
|
83
83
|
// try global
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
try {
|
|
85
|
+
let { default: plugin } = await importGlobalSilent(name);
|
|
86
|
+
if (plugin) {
|
|
87
|
+
let p = new plugin(options, context, queue);
|
|
88
|
+
plugins.push(p);
|
|
89
|
+
} else {
|
|
90
|
+
console.error("Couldn't load plugin %s: %s", name, error_);
|
|
91
|
+
// if it fails here, let it fail hard
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
console.error("Couldn't find/load plugin %s", name);
|
|
90
96
|
// if it fails here, let it fail hard
|
|
91
97
|
throw error;
|
|
92
98
|
}
|
|
@@ -3,6 +3,8 @@ import { parse } from 'node:url';
|
|
|
3
3
|
import { default as _merge } from 'lodash.merge';
|
|
4
4
|
|
|
5
5
|
import { getLogger } from '@sitespeed.io/log';
|
|
6
|
+
import { configureLogging } from 'browsertime';
|
|
7
|
+
|
|
6
8
|
const log = getLogger('plugin.browsertime');
|
|
7
9
|
|
|
8
10
|
import dayjs from 'dayjs';
|
|
@@ -88,10 +90,10 @@ export default class BrowsertimePlugin extends SitespeedioPlugin {
|
|
|
88
90
|
'browsertime.run'
|
|
89
91
|
);
|
|
90
92
|
this.axeAggregatorTotal = new AxeAggregator(this.options);
|
|
93
|
+
configureLogging(options);
|
|
91
94
|
}
|
|
95
|
+
|
|
92
96
|
async processMessage(message) {
|
|
93
|
-
const { configureLogging } = await import('browsertime');
|
|
94
|
-
configureLogging(this.options);
|
|
95
97
|
const options = this.options;
|
|
96
98
|
switch (message.type) {
|
|
97
99
|
// When sistespeed.io starts, a setup messages is posted on the queue
|
|
@@ -1,69 +1,95 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import jrp from 'junit-report-builder';
|
|
5
|
-
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import merge from 'lodash.merge';
|
|
6
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
|
+
|
|
7
6
|
const log = getLogger('sitespeedio.plugin.budget');
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Escapes XML special characters.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} str - The text to escape.
|
|
12
|
+
* @returns {string} The escaped text.
|
|
13
|
+
*/
|
|
14
|
+
function xmlEscape(str) {
|
|
15
|
+
return String(str)
|
|
16
|
+
.replaceAll('&', '&')
|
|
17
|
+
.replaceAll('<', '<')
|
|
18
|
+
.replaceAll('>', '>')
|
|
19
|
+
.replaceAll('"', '"')
|
|
20
|
+
.replaceAll("'", ''');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wraps a string in a CDATA block.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} str - The string to wrap.
|
|
27
|
+
* @returns {string} The CDATA-wrapped string.
|
|
28
|
+
*/
|
|
29
|
+
function cdata(str) {
|
|
30
|
+
return `<![CDATA[${str}]]>`;
|
|
31
|
+
}
|
|
10
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Writes a JUnit XML report mimicking the original output.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} results - Object containing `failing` and `working` results.
|
|
37
|
+
* @param {string} dir - Directory where `junit.xml` will be written.
|
|
38
|
+
* @param {object} options - Options (expects `options.budget.friendlyName`).
|
|
39
|
+
*/
|
|
11
40
|
export function writeJunit(results, dir, options) {
|
|
12
|
-
|
|
13
|
-
const
|
|
41
|
+
const failing = results.failing || {};
|
|
42
|
+
const working = results.working || {};
|
|
43
|
+
const urls = Object.keys(merge({}, failing, working));
|
|
14
44
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (url.startsWith('http')) {
|
|
19
|
-
const parsedUrl = parse(url);
|
|
20
|
-
name = url.startsWith('http') ? url : url;
|
|
21
|
-
parsedUrl.hostname.replaceAll('.', '_') +
|
|
22
|
-
'.' +
|
|
23
|
-
parsedUrl.path.replaceAll('.', '_').replaceAll('/', '_');
|
|
24
|
-
}
|
|
45
|
+
let totalTests = 0;
|
|
46
|
+
let totalFailures = 0;
|
|
47
|
+
let suitesXml = '';
|
|
25
48
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
for (const url of urls) {
|
|
50
|
+
const suiteName = `${options.budget.friendlyName || 'sitespeed.io'}.${url}`;
|
|
51
|
+
let suiteTests = 0;
|
|
52
|
+
let suiteFailures = 0;
|
|
53
|
+
let testCasesXml = '';
|
|
29
54
|
|
|
30
|
-
if (
|
|
31
|
-
for (const result of
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
' ' +
|
|
42
|
-
result.friendlyLimit ||
|
|
43
|
-
result.limit + ' ' + url
|
|
44
|
-
);
|
|
55
|
+
if (failing[url]) {
|
|
56
|
+
for (const result of failing[url]) {
|
|
57
|
+
suiteTests++;
|
|
58
|
+
totalTests++;
|
|
59
|
+
suiteFailures++;
|
|
60
|
+
totalFailures++;
|
|
61
|
+
const testCaseName = `${result.type}.${result.metric}`;
|
|
62
|
+
const failureMessage = `${result.metric} is ${result.friendlyValue || result.value}`;
|
|
63
|
+
testCasesXml += ` <testcase classname="${xmlEscape(url)}" name="${xmlEscape(testCaseName)}">\n`;
|
|
64
|
+
testCasesXml += ` <failure message="${xmlEscape(failureMessage)}"/>\n`;
|
|
65
|
+
testCasesXml += ` </testcase>\n`;
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
if (
|
|
49
|
-
for (const result of
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
' and limit ' +
|
|
58
|
-
result.limitType +
|
|
59
|
-
' ' +
|
|
60
|
-
result.friendlyLimit ||
|
|
61
|
-
result.limit + ' ' + url
|
|
62
|
-
);
|
|
69
|
+
if (working[url]) {
|
|
70
|
+
for (const result of working[url]) {
|
|
71
|
+
suiteTests++;
|
|
72
|
+
totalTests++;
|
|
73
|
+
const testCaseName = `${result.type}.${result.metric}`;
|
|
74
|
+
const systemOutMessage = `${result.metric} is ${result.friendlyValue || result.value}`;
|
|
75
|
+
testCasesXml += ` <testcase classname="${xmlEscape(url)}" name="${xmlEscape(testCaseName)}">\n`;
|
|
76
|
+
testCasesXml += ` <system-out>${cdata(systemOutMessage)}</system-out>\n`;
|
|
77
|
+
testCasesXml += ` </testcase>\n`;
|
|
63
78
|
}
|
|
64
79
|
}
|
|
80
|
+
|
|
81
|
+
suitesXml += ` <testsuite name="${xmlEscape(suiteName)}" tests="${suiteTests}" failures="${suiteFailures}" errors="0" skipped="0">\n`;
|
|
82
|
+
suitesXml += testCasesXml;
|
|
83
|
+
suitesXml += ` </testsuite>\n`;
|
|
65
84
|
}
|
|
85
|
+
|
|
86
|
+
const xml =
|
|
87
|
+
`<?xml version="1.0" encoding="UTF-8"?>\n` +
|
|
88
|
+
`<testsuites tests="${totalTests}" failures="${totalFailures}" errors="0" skipped="0">\n` +
|
|
89
|
+
suitesXml +
|
|
90
|
+
`</testsuites>\n`;
|
|
91
|
+
|
|
66
92
|
const file = path.join(dir, 'junit.xml');
|
|
67
93
|
log.info('Write junit budget to %s', path.resolve(file));
|
|
68
|
-
|
|
94
|
+
fs.writeFileSync(file, xml);
|
|
69
95
|
}
|
|
@@ -1,36 +1,44 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { EOL } from 'node:os';
|
|
4
|
-
import tap from 'tape';
|
|
5
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
|
+
|
|
6
6
|
const log = getLogger('sitespeedio.plugin.budget');
|
|
7
7
|
|
|
8
8
|
export function writeTap(results, dir) {
|
|
9
9
|
const file = path.join(dir, 'budget.tap');
|
|
10
10
|
log.info('Write budget to %s', path.resolve(file));
|
|
11
|
-
const tapOutput = fs.createWriteStream(file);
|
|
12
|
-
tap.createStream().pipe(tapOutput);
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const lines = [];
|
|
13
|
+
lines.push('TAP version 13');
|
|
14
|
+
let testCount = 0;
|
|
16
15
|
|
|
16
|
+
// Iterate over each result group (e.g. "passing" and "failing")
|
|
17
|
+
for (const resultType of Object.keys(results)) {
|
|
18
|
+
const group = results[resultType];
|
|
19
|
+
if (!group) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const urls = Object.keys(group);
|
|
17
23
|
for (const url of urls) {
|
|
18
|
-
for (const result of
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
24
|
+
for (const result of group[url]) {
|
|
25
|
+
testCount += 1;
|
|
26
|
+
const testTitle = `${result.type}.${result.metric} ${url}`;
|
|
27
|
+
let extra = '';
|
|
28
|
+
if (resultType === 'failing') {
|
|
29
|
+
extra = ` limit ${result.limitType} ${result.friendlyLimit || result.limit}`;
|
|
30
|
+
}
|
|
31
|
+
const valueDisplay = result.friendlyValue || result.value;
|
|
32
|
+
|
|
33
|
+
lines.push(`# ${testTitle}`);
|
|
34
|
+
const status = resultType === 'failing' ? 'not ok' : 'ok';
|
|
35
|
+
lines.push(
|
|
36
|
+
`${status} ${testCount} ${testTitle} ${valueDisplay}${extra ? ` ${extra}` : ''}`
|
|
37
|
+
);
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
}
|
|
41
|
+
|
|
42
|
+
lines.push(`1..${testCount}`);
|
|
43
|
+
fs.writeFileSync(file, lines.join(EOL) + EOL);
|
|
36
44
|
}
|
|
@@ -128,15 +128,17 @@ export default class PageXrayPlugin extends SitespeedioPlugin {
|
|
|
128
128
|
);
|
|
129
129
|
}
|
|
130
130
|
} else {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
// Check that we actually have one tested page
|
|
132
|
+
if (pageSummary.length > 0) {
|
|
133
|
+
pageSummary[0].statistics =
|
|
134
|
+
this.pageXrayAggregator.summarizePerUrl(message.url);
|
|
135
|
+
queue.postMessage(
|
|
136
|
+
make('pagexray.pageSummary', pageSummary[0], {
|
|
137
|
+
url: message.url,
|
|
138
|
+
group // TODO get the group from the URL?
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
}
|
|
140
142
|
let iteration = 1;
|
|
141
143
|
for (let summary of pageSummary) {
|
|
142
144
|
queue.postMessage(
|
|
@@ -140,7 +140,8 @@ export class PageXrayAggregator {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
summarizePerUrl(url) {
|
|
143
|
-
|
|
143
|
+
// If we do not have a tested working page there's nothing to summarize
|
|
144
|
+
return this.urls[url] ? this.summarizePerObject(this.urls[url]) : {};
|
|
144
145
|
}
|
|
145
146
|
summarize() {
|
|
146
147
|
if (Object.keys(this.stats).length === 0) {
|