phantomas 2.2.0 → 2.5.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/Dockerfile +32 -29
- package/README.md +13 -2
- package/bin/program.js +8 -0
- package/core/modules/navigationTiming/scope.js +2 -1
- package/core/scope.js +2 -1
- package/extensions/pageSource/pageSource.js +2 -0
- package/extensions/pageStorage/pageStorage.js +82 -0
- package/extensions/scroll/scroll.js +3 -0
- package/hooks/build +1 -1
- package/lib/browser.js +14 -8
- package/lib/index.js +7 -3
- package/lib/metadata/metadata.json +17 -7
- package/modules/ajaxRequests/scope.js +1 -1
- package/modules/analyzeCss/analyzeCss.js +77 -78
- package/modules/analyzeCss/scope.js +1 -1
- package/modules/cacheHits/cacheHits.js +6 -3
- package/modules/documentHeight/scope.js +1 -1
- package/modules/domComplexity/scope.js +1 -1
- package/modules/domHiddenContent/scope.js +1 -1
- package/modules/domMutations/scope.js +1 -1
- package/modules/domQueries/scope.js +1 -1
- package/modules/domains/domains.js +1 -1
- package/modules/events/scope.js +2 -1
- package/modules/globalVariables/scope.js +1 -1
- package/modules/jQuery/scope.js +1 -1
- package/modules/javaScriptBottlenecks/scope.js +1 -1
- package/modules/lazyLoadableImages/scope.js +1 -1
- package/modules/localStorage/scope.js +1 -1
- package/modules/protocols/protocols.js +21 -4
- package/modules/requestsStats/requestsStats.js +1 -1
- package/modules/staticAssets/staticAssets.js +2 -1
- package/modules/windowPerformance/windowPerformance.js +1 -0
- package/package.json +28 -18
- package/lib/fast-stats.js +0 -634
|
@@ -69,7 +69,7 @@ module.exports = function (phantomas) {
|
|
|
69
69
|
return f + str.substr(1);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function analyzeCss(css, context
|
|
72
|
+
async function analyzeCss(css, context) {
|
|
73
73
|
/**
|
|
74
74
|
// force JSON output format
|
|
75
75
|
options.push('--json');
|
|
@@ -94,90 +94,89 @@ module.exports = function (phantomas) {
|
|
|
94
94
|
|
|
95
95
|
// https://www.npmjs.com/package/analyze-css#commonjs-module
|
|
96
96
|
var options = {};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
// Error string returned (stderror)
|
|
113
|
-
if (
|
|
114
|
-
err.indexOf("CSS parsing failed") > 0 ||
|
|
115
|
-
err.indexOf("is an invalid expression") > 0
|
|
116
|
-
) {
|
|
117
|
-
offender += " (" + err.trim() + ")";
|
|
118
|
-
} else if (err.indexOf("Empty CSS was provided") > 0) {
|
|
119
|
-
offender += " (Empty CSS was provided)";
|
|
120
|
-
}
|
|
97
|
+
let results;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
results = await analyzer(css, options);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
phantomas.log("analyzeCss: sub-process failed! - %s", err);
|
|
103
|
+
|
|
104
|
+
// report failed CSS parsing (issue #494(
|
|
105
|
+
var offender = offenderSrc;
|
|
106
|
+
if (err.message) {
|
|
107
|
+
// Error object returned
|
|
108
|
+
if (err.message.indexOf("Unable to parse JSON string") > 0) {
|
|
109
|
+
offender += " (analyzeCss output error)";
|
|
121
110
|
}
|
|
111
|
+
} else {
|
|
112
|
+
// Error string returned (stderror)
|
|
113
|
+
if (
|
|
114
|
+
err.indexOf("CSS parsing failed") > 0 ||
|
|
115
|
+
err.indexOf("is an invalid expression") > 0
|
|
116
|
+
) {
|
|
117
|
+
offender += " (" + err.trim() + ")";
|
|
118
|
+
} else if (err.indexOf("Empty CSS was provided") > 0) {
|
|
119
|
+
offender += " (Empty CSS was provided)";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
phantomas.incrMetric("cssParsingErrors");
|
|
124
|
+
phantomas.addOffender("cssParsingErrors", offender);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
128
|
+
var offenderSrc = context || "[inline CSS]";
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
"Got results for %s from %s",
|
|
132
|
-
offenderSrc,
|
|
133
|
-
results.generator
|
|
134
|
-
);
|
|
130
|
+
phantomas.log("Got results for %s from %s", offenderSrc, results.generator);
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
const metrics = results.metrics,
|
|
133
|
+
offenders = results.offenders;
|
|
138
134
|
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
Object.keys(metrics).forEach((metric) => {
|
|
136
|
+
var metricPrefixed = "css" + ucfirst(metric);
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
if (/Avg$/.test(metricPrefixed)) {
|
|
139
|
+
// update the average value (see #641)
|
|
140
|
+
phantomas.addToAvgMetric(metricPrefixed, metrics[metric]);
|
|
141
|
+
} else {
|
|
142
|
+
// increase metrics
|
|
143
|
+
phantomas.incrMetric(metricPrefixed, metrics[metric]);
|
|
144
|
+
}
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
// and add offenders
|
|
147
|
+
if (typeof offenders[metric] !== "undefined") {
|
|
148
|
+
offenders[metric].forEach((offender) => {
|
|
149
|
+
phantomas.addOffender(metricPrefixed, {
|
|
150
|
+
url: offenderSrc,
|
|
151
|
+
value: {
|
|
152
|
+
message: offender.message,
|
|
153
|
+
position: {
|
|
154
|
+
...offender.position,
|
|
155
|
+
source: offender.source || "undefined",
|
|
156
|
+
}, // cast to object
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// add more offenders (#578)
|
|
162
|
+
else {
|
|
163
|
+
switch (metricPrefixed) {
|
|
164
|
+
case "cssLength":
|
|
165
|
+
case "cssRules":
|
|
166
|
+
case "cssSelectors":
|
|
167
|
+
case "cssDeclarations":
|
|
168
|
+
case "cssNotMinified":
|
|
169
|
+
case "cssSelectorLengthAvg":
|
|
170
|
+
case "cssSpecificityIdAvg":
|
|
171
|
+
case "cssSpecificityClassAvg":
|
|
172
|
+
case "cssSpecificityTagAvg":
|
|
153
173
|
phantomas.addOffender(metricPrefixed, {
|
|
154
174
|
url: offenderSrc,
|
|
155
|
-
value:
|
|
175
|
+
value: metrics[metric],
|
|
156
176
|
});
|
|
157
|
-
|
|
177
|
+
break;
|
|
158
178
|
}
|
|
159
|
-
|
|
160
|
-
else {
|
|
161
|
-
switch (metricPrefixed) {
|
|
162
|
-
case "cssLength":
|
|
163
|
-
case "cssRules":
|
|
164
|
-
case "cssSelectors":
|
|
165
|
-
case "cssDeclarations":
|
|
166
|
-
case "cssNotMinified":
|
|
167
|
-
case "cssSelectorLengthAvg":
|
|
168
|
-
case "cssSpecificityIdAvg":
|
|
169
|
-
case "cssSpecificityClassAvg":
|
|
170
|
-
case "cssSpecificityTagAvg":
|
|
171
|
-
phantomas.addOffender(metricPrefixed, {
|
|
172
|
-
url: offenderSrc,
|
|
173
|
-
value: metrics[metric],
|
|
174
|
-
});
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
callback();
|
|
179
|
+
}
|
|
181
180
|
});
|
|
182
181
|
}
|
|
183
182
|
|
|
@@ -195,12 +194,12 @@ module.exports = function (phantomas) {
|
|
|
195
194
|
|
|
196
195
|
// ok, now let's analyze the collect CSS
|
|
197
196
|
phantomas.on("beforeClose", () => {
|
|
198
|
-
|
|
197
|
+
const promises = [];
|
|
199
198
|
|
|
200
199
|
stylesheets.forEach((entry) => {
|
|
201
200
|
promises.push(
|
|
202
201
|
new Promise(async (resolve) => {
|
|
203
|
-
|
|
202
|
+
let css = entry.inline;
|
|
204
203
|
phantomas.log("Analyzing %s", entry.url || "inline CSS");
|
|
205
204
|
|
|
206
205
|
if (entry.content) {
|
|
@@ -208,10 +207,10 @@ module.exports = function (phantomas) {
|
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
if (css) {
|
|
211
|
-
analyzeCss(css, entry.url
|
|
212
|
-
} else {
|
|
213
|
-
resolve();
|
|
210
|
+
await analyzeCss(css, entry.url);
|
|
214
211
|
}
|
|
212
|
+
|
|
213
|
+
resolve();
|
|
215
214
|
})
|
|
216
215
|
);
|
|
217
216
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function (phantomas) {
|
|
1
|
+
(function analyzeCssScope(phantomas) {
|
|
2
2
|
document.addEventListener("DOMContentLoaded", function () {
|
|
3
3
|
// both DOM and CSSOM are constructed, no stylesheets are blocking JavaScript execution
|
|
4
4
|
phantomas.spyEnabled(false, "Checking inline scripts");
|
|
@@ -9,15 +9,18 @@ module.exports = function (phantomas) {
|
|
|
9
9
|
phantomas.setMetric("cachePasses"); // @desc number of cache passes @offenders
|
|
10
10
|
|
|
11
11
|
phantomas.on("recv", (entry) => {
|
|
12
|
-
|
|
12
|
+
let isHit, isMiss, isPass;
|
|
13
13
|
|
|
14
14
|
// parser response headers
|
|
15
15
|
//
|
|
16
16
|
// X-Cache:HIT, HIT
|
|
17
17
|
// X-Cache:arsenic miss (0)
|
|
18
18
|
// Age: 170221
|
|
19
|
-
age =
|
|
20
|
-
|
|
19
|
+
const age =
|
|
20
|
+
typeof entry.headers.Age !== "undefined"
|
|
21
|
+
? parseInt(entry.headers.Age, 10)
|
|
22
|
+
: undefined,
|
|
23
|
+
xCacheHeader = (entry.headers["X-Cache"] || "").toLowerCase();
|
|
21
24
|
|
|
22
25
|
if (xCacheHeader !== "") {
|
|
23
26
|
isHit = xCacheHeader.indexOf("hit") > -1;
|
package/modules/events/scope.js
CHANGED
package/modules/jQuery/scope.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = function (phantomas) {
|
|
7
7
|
var domains = new Map(),
|
|
8
|
+
mainDomain = undefined,
|
|
8
9
|
beforeDomReady = true;
|
|
9
10
|
|
|
10
11
|
phantomas.setMetric("mainDomainHttpProtocol"); // @desc HTTP protocol used by the main domain [string]
|
|
11
12
|
phantomas.setMetric("oldHttpProtocol"); // @desc number of domains using HTTP/1.0 or 1.1
|
|
12
13
|
phantomas.setMetric("mainDomainTlsProtocol"); // @desc TLS protocol used by the main domain [string]
|
|
13
|
-
phantomas.setMetric("oldTlsProtocol"); // @desc number of domains using TLS 1.
|
|
14
|
+
phantomas.setMetric("oldTlsProtocol"); // @desc number of domains using TLS 1.2
|
|
14
15
|
|
|
15
16
|
// spy all requests
|
|
16
17
|
phantomas.on("recv", (entry) => {
|
|
@@ -18,7 +19,14 @@ module.exports = function (phantomas) {
|
|
|
18
19
|
var domain = (entry.isSSL ? "https://" : "http://") + entry.domain;
|
|
19
20
|
|
|
20
21
|
if (domains.size === 0) {
|
|
22
|
+
mainDomain = domain;
|
|
23
|
+
phantomas.log("Our main domain is now %s", mainDomain);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (domain == mainDomain) {
|
|
21
27
|
// our first request represents the main domain
|
|
28
|
+
// h3 protocol is used for subsequent requests for the same domain
|
|
29
|
+
// we need to keep updating these metrics on each response we get
|
|
22
30
|
phantomas.setMetric("mainDomainHttpProtocol", entry.httpVersion);
|
|
23
31
|
phantomas.setMetric("mainDomainTlsProtocol", entry.tlsVersion);
|
|
24
32
|
}
|
|
@@ -41,6 +49,11 @@ module.exports = function (phantomas) {
|
|
|
41
49
|
} else {
|
|
42
50
|
// just increment the number of requests
|
|
43
51
|
domains.get(domain).requests++;
|
|
52
|
+
|
|
53
|
+
// h3 protocol is used for subsequent requests for the same domain
|
|
54
|
+
// initial ones are performed using h2
|
|
55
|
+
domains.get(domain).httpVersion = entry.httpVersion;
|
|
56
|
+
domains.get(domain).tlsVersion = entry.tlsVersion;
|
|
44
57
|
}
|
|
45
58
|
}
|
|
46
59
|
});
|
|
@@ -55,7 +68,7 @@ module.exports = function (phantomas) {
|
|
|
55
68
|
// set metrics
|
|
56
69
|
phantomas.on("report", () => {
|
|
57
70
|
domains.forEach(function (value, key) {
|
|
58
|
-
// As of 2020, h2 is the latest protocol, h3 is coming
|
|
71
|
+
// As of 2020, h2 is the latest protocol, h3 is coming in 2021
|
|
59
72
|
if (value.httpVersion.indexOf("http/1") === 0) {
|
|
60
73
|
phantomas.incrMetric("oldHttpProtocol");
|
|
61
74
|
phantomas.addOffender("oldHttpProtocol", {
|
|
@@ -68,9 +81,13 @@ module.exports = function (phantomas) {
|
|
|
68
81
|
// As of 2020, TLS 1.3 is the latest protocol and it brings speed improvements over 1.2
|
|
69
82
|
if (value.tlsVersion) {
|
|
70
83
|
// parse version number
|
|
71
|
-
var tlsVersion =
|
|
84
|
+
var tlsVersion =
|
|
85
|
+
value.tlsVersion === "QUIC"
|
|
86
|
+
? "quic"
|
|
87
|
+
: parseFloat(value.tlsVersion.substring(4));
|
|
88
|
+
phantomas.log(`tlsVersion for ${key} domain is ${tlsVersion}`);
|
|
72
89
|
|
|
73
|
-
if (tlsVersion < 1.3) {
|
|
90
|
+
if (tlsVersion !== "quic" && tlsVersion < 1.3) {
|
|
74
91
|
phantomas.incrMetric("oldTlsProtocol");
|
|
75
92
|
phantomas.addOffender("oldTlsProtocol", {
|
|
76
93
|
domain: key,
|
|
@@ -11,7 +11,8 @@ module.exports = function (phantomas) {
|
|
|
11
11
|
assetsReqCounter = new Collection(),
|
|
12
12
|
cookieDomains = new Collection(),
|
|
13
13
|
// TODO: use 3pc database with tracking services
|
|
14
|
-
trackingUrls =
|
|
14
|
+
trackingUrls =
|
|
15
|
+
/google-analytics.com\/__utm.gif|pixel.quantserve.com\/pixel/;
|
|
15
16
|
|
|
16
17
|
phantomas.setMetric("assetsNotGzipped"); // @desc number of static assets that were not gzipped
|
|
17
18
|
phantomas.setMetric("assetsWithQueryString"); // @desc number of static assets requested with query string (e.g. ?foo) in URL
|
|
@@ -25,6 +25,7 @@ module.exports = function (phantomas) {
|
|
|
25
25
|
phantomas.setMetric("timeFrontend"); // @desc time to window.load compared to the total loading time [%]
|
|
26
26
|
|
|
27
27
|
phantomas.on("beforeClose", async function () {
|
|
28
|
+
/* istanbul ignore next */
|
|
28
29
|
const timing = await phantomas.evaluate(() => {
|
|
29
30
|
return window.performance.timing.toJSON();
|
|
30
31
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phantomas",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"author": "macbre <maciej.brencz@gmail.com> (http://macbre.net)",
|
|
5
5
|
"description": "Headless Chromium-based web performance metrics collector and monitoring tool",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -24,37 +24,47 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "BSD-2-Clause",
|
|
26
26
|
"engines": {
|
|
27
|
-
"node": ">=
|
|
27
|
+
"node": ">=14.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"analyze-css": "^0.
|
|
31
|
-
"
|
|
32
|
-
"ansistyles": "~0.1.0",
|
|
33
|
-
"ascii-table": "0.0.9",
|
|
34
|
-
"async": "^3.2.0",
|
|
35
|
-
"commander": "^7.0.0",
|
|
36
|
-
"csv-string": "^4.0.1",
|
|
30
|
+
"analyze-css": "^2.0.0",
|
|
31
|
+
"commander": "^9.0.0",
|
|
37
32
|
"debug": "^4.1.1",
|
|
38
33
|
"decamelize": "^5.0.0",
|
|
34
|
+
"fast-stats": "0.0.6",
|
|
39
35
|
"js-yaml": "^4.0.0",
|
|
40
|
-
"
|
|
41
|
-
"puppeteer": "^8.0.0"
|
|
36
|
+
"puppeteer": "^14.4.1"
|
|
42
37
|
},
|
|
43
38
|
"devDependencies": {
|
|
44
|
-
"
|
|
39
|
+
"@jest/globals": "^28.0.0",
|
|
40
|
+
"eslint": "^8.0.0",
|
|
45
41
|
"eslint-config-prettier": "^8.1.0",
|
|
46
42
|
"eslint-plugin-node": "^11.1.0",
|
|
47
|
-
"glob": "^
|
|
48
|
-
"
|
|
49
|
-
"prettier": "2.
|
|
50
|
-
"vows": "^0.8.3"
|
|
43
|
+
"glob": "^8.0.1",
|
|
44
|
+
"jest": "^28.0.0",
|
|
45
|
+
"prettier": "2.7.1"
|
|
51
46
|
},
|
|
52
47
|
"scripts": {
|
|
53
|
-
"test": "
|
|
54
|
-
"unit-test": "
|
|
48
|
+
"test": "node --trace-warnings node_modules/.bin/jest test/ --coverage --detectOpenHandles --forceExit",
|
|
49
|
+
"unit-test": "jest test/results.test.js test/modules/ --coverage --detectOpenHandles --forceExit",
|
|
55
50
|
"lint": "eslint --cache .",
|
|
56
51
|
"metadata": "DEBUG=generate node lib/metadata/generate.js",
|
|
57
52
|
"make-docs": "DEBUG=docs node lib/metadata/make_docs.js",
|
|
58
53
|
"prettier": "npx prettier --write ."
|
|
54
|
+
},
|
|
55
|
+
"jest": {
|
|
56
|
+
"verbose": true,
|
|
57
|
+
"reporters": [
|
|
58
|
+
"default",
|
|
59
|
+
"github-actions"
|
|
60
|
+
],
|
|
61
|
+
"coverageThreshold": {
|
|
62
|
+
"global": {
|
|
63
|
+
"statements": 89,
|
|
64
|
+
"branches": 85,
|
|
65
|
+
"functions": 89,
|
|
66
|
+
"lines": 89
|
|
67
|
+
}
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
70
|
}
|