phantomas 2.3.0 → 2.4.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 +1 -1
- package/bin/program.js +8 -0
- package/extensions/pageStorage/pageStorage.js +82 -0
- package/hooks/build +1 -1
- package/lib/index.js +1 -1
- package/modules/analyzeCss/analyzeCss.js +77 -78
- package/package.json +9 -15
package/Dockerfile
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
# https://hub.docker.com/_/node
|
|
2
|
-
FROM node:lts-
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
FROM node:lts-bullseye-slim
|
|
3
|
+
|
|
4
|
+
# install dependencies of Chrome binary that will be fetched by npm ci
|
|
5
|
+
RUN apt-get update \
|
|
6
|
+
&& apt-get install -y --no-install-recommends \
|
|
7
|
+
fonts-liberation \
|
|
8
|
+
libasound2 \
|
|
9
|
+
libatk-bridge2.0-0 \
|
|
10
|
+
libatk1.0-0 \
|
|
11
|
+
libatspi2.0-0 \
|
|
12
|
+
libc6 \
|
|
13
|
+
libcairo2 \
|
|
14
|
+
libcups2 \
|
|
15
|
+
libdbus-1-3 \
|
|
16
|
+
libfreetype6 \
|
|
17
|
+
libgbm1 \
|
|
18
|
+
libharfbuzz0b \
|
|
19
|
+
libnss3 \
|
|
20
|
+
libpango-1.0-0 \
|
|
21
|
+
libx11-6 \
|
|
22
|
+
libxext6 \
|
|
23
|
+
libxkbcommon0 \
|
|
24
|
+
x11-utils \
|
|
25
|
+
xdg-utils \
|
|
26
|
+
zlib1g \
|
|
27
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
24
28
|
|
|
25
29
|
# Set up a working directory
|
|
26
30
|
ENV HOME /opt/phantomas
|
|
@@ -30,11 +34,6 @@ RUN chown -R nobody:nogroup .
|
|
|
30
34
|
# Run everything after as non-privileged user.
|
|
31
35
|
USER nobody
|
|
32
36
|
|
|
33
|
-
# Tell Puppeteer to skip installing Chrome. We'll be using the installed binary
|
|
34
|
-
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
|
35
|
-
|
|
36
|
-
# Tell phantomas where Chromium binary is and that we're in docker
|
|
37
|
-
ENV PHANTOMAS_CHROMIUM_EXECUTABLE /usr/bin/chromium-browser
|
|
38
37
|
ENV DOCKERIZED yes
|
|
39
38
|
|
|
40
39
|
# Install dependencies
|
|
@@ -42,8 +41,12 @@ COPY package.json .
|
|
|
42
41
|
COPY package-lock.json .
|
|
43
42
|
RUN npm ci
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
# TODO: find the chrome binary and symlink it to the PATH
|
|
45
|
+
RUN ldd $(find . -wholename '*chrome-linux/chrome') && \
|
|
46
|
+
$(find . -wholename '*chrome-linux/chrome') --version
|
|
47
|
+
|
|
48
|
+
ARG GITHUB_SHA="dev"
|
|
49
|
+
ENV COMMIT_SHA ${GITHUB_SHA}
|
|
47
50
|
|
|
48
51
|
# label the image with branch name and commit hash
|
|
49
52
|
LABEL maintainer="maciej.brencz@gmail.com"
|
package/README.md
CHANGED
package/bin/program.js
CHANGED
|
@@ -68,6 +68,14 @@ function getProgram() {
|
|
|
68
68
|
"--cookies-file <file>",
|
|
69
69
|
"specifies the file name to store the persistent Cookies"
|
|
70
70
|
)
|
|
71
|
+
.option(
|
|
72
|
+
"--local-storage <values>",
|
|
73
|
+
'ability to set a local storage, key-value pairs (e.g. "bar=foo;domain=url")'
|
|
74
|
+
)
|
|
75
|
+
.option(
|
|
76
|
+
"--session-storage <values>",
|
|
77
|
+
'ability to set a session storage, key-value pairs (e.g. "bar=foo;domain=url")'
|
|
78
|
+
)
|
|
71
79
|
.option(
|
|
72
80
|
"--ignore-ssl-errors",
|
|
73
81
|
"ignores SSL errors, such as expired or self-signed certificate errors"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Support for session-/localStorage injection.
|
|
3
|
+
*/
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
module.exports = function (phantomas) {
|
|
7
|
+
const SESSION_STORAGE = "session-storage",
|
|
8
|
+
LOCAL_STORAGE = "local-storage";
|
|
9
|
+
|
|
10
|
+
phantomas.on("init", async (page) => {
|
|
11
|
+
let sessionStorage = phantomas.getParam(SESSION_STORAGE, false);
|
|
12
|
+
let localStorage = phantomas.getParam(LOCAL_STORAGE, false);
|
|
13
|
+
|
|
14
|
+
// Mapping given "storage-string" to json object if string has been given
|
|
15
|
+
if (typeof sessionStorage == "string" || sessionStorage instanceof String) {
|
|
16
|
+
sessionStorage = parseStorage(sessionStorage);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof localStorage == "string" || localStorage instanceof String) {
|
|
20
|
+
localStorage = parseStorage(localStorage);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (sessionStorage) {
|
|
24
|
+
phantomas.log(
|
|
25
|
+
"Injecting sessionStorage: %j",
|
|
26
|
+
JSON.stringify(sessionStorage)
|
|
27
|
+
);
|
|
28
|
+
await injectStorage(page, sessionStorage, SESSION_STORAGE);
|
|
29
|
+
}
|
|
30
|
+
if (localStorage) {
|
|
31
|
+
phantomas.log("Injecting localStorag: %j", JSON.stringify(localStorage));
|
|
32
|
+
await injectStorage(page, localStorage, LOCAL_STORAGE);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function parseStorage(storageString) {
|
|
37
|
+
// --sessionStorage='bar=foo;domain=url'
|
|
38
|
+
// --localStorage='bar=fooLocal;domain=urlLocal'
|
|
39
|
+
var storageMap = {};
|
|
40
|
+
storageString.split(";").forEach(function (singleEntry) {
|
|
41
|
+
var entryKeyValue = singleEntry.split("=");
|
|
42
|
+
storageMap[entryKeyValue[0]] = entryKeyValue[1];
|
|
43
|
+
});
|
|
44
|
+
return storageMap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Inject the given storage into the specified page storage.
|
|
49
|
+
* Either localStorage or sessionStorage
|
|
50
|
+
*
|
|
51
|
+
* @param {Page} page in which page the storage should be injected
|
|
52
|
+
* @param {Object} storage the JSON object consisting of the storage keys and values
|
|
53
|
+
* @param {string} storageType either localStorage or sessionStorage
|
|
54
|
+
*/
|
|
55
|
+
async function injectStorage(page, storage, storageType) {
|
|
56
|
+
if (!page || !storage || !storageType) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* istanbul ignore next */
|
|
61
|
+
await page.evaluateOnNewDocument(
|
|
62
|
+
(storage, storageType, SESSION_STORAGE, LOCAL_STORAGE) => {
|
|
63
|
+
const keys = Object.keys(storage);
|
|
64
|
+
const values = Object.values(storage);
|
|
65
|
+
if (storageType === SESSION_STORAGE) {
|
|
66
|
+
for (let i = 0; i < keys.length; i++) {
|
|
67
|
+
sessionStorage.setItem(keys[i], values[i]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (storageType === LOCAL_STORAGE) {
|
|
71
|
+
for (let i = 0; i < keys.length; i++) {
|
|
72
|
+
localStorage.setItem(keys[i], values[i]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
storage,
|
|
77
|
+
storageType,
|
|
78
|
+
SESSION_STORAGE,
|
|
79
|
+
LOCAL_STORAGE
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
};
|
package/hooks/build
CHANGED
package/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ function phantomas(url, opts) {
|
|
|
39
39
|
|
|
40
40
|
debug("Options: %s", JSON.stringify(options));
|
|
41
41
|
|
|
42
|
-
events.setMaxListeners(
|
|
42
|
+
events.setMaxListeners(250); // MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
|
|
43
43
|
|
|
44
44
|
var results = new Results();
|
|
45
45
|
results.setUrl(url);
|
|
@@ -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
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phantomas",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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,35 +24,29 @@
|
|
|
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": "^
|
|
31
|
-
"ansicolors": "~0.3.2",
|
|
32
|
-
"ansistyles": "~0.1.0",
|
|
33
|
-
"ascii-table": "0.0.9",
|
|
34
|
-
"async": "^3.2.0",
|
|
30
|
+
"analyze-css": "^2.0.0",
|
|
35
31
|
"commander": "^8.0.0",
|
|
36
|
-
"csv-string": "^4.0.1",
|
|
37
32
|
"debug": "^4.1.1",
|
|
38
33
|
"decamelize": "^5.0.0",
|
|
39
34
|
"fast-stats": "0.0.6",
|
|
40
35
|
"js-yaml": "^4.0.0",
|
|
41
|
-
"
|
|
42
|
-
"puppeteer": "^10.0.0"
|
|
36
|
+
"puppeteer": "^13.0.0"
|
|
43
37
|
},
|
|
44
38
|
"devDependencies": {
|
|
45
|
-
"
|
|
39
|
+
"@jest/globals": "^27.4.0",
|
|
40
|
+
"eslint": "^8.0.0",
|
|
46
41
|
"eslint-config-prettier": "^8.1.0",
|
|
47
42
|
"eslint-plugin-node": "^11.1.0",
|
|
48
43
|
"glob": "^7.1.6",
|
|
49
44
|
"jest": "^27.0.1",
|
|
50
|
-
"
|
|
51
|
-
"prettier": "2.3.2"
|
|
45
|
+
"prettier": "2.5.1"
|
|
52
46
|
},
|
|
53
47
|
"scripts": {
|
|
54
|
-
"test": "jest test/ --coverage --detectOpenHandles",
|
|
55
|
-
"unit-test": "jest test/results.test.js test/modules/ --coverage --detectOpenHandles",
|
|
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",
|
|
56
50
|
"lint": "eslint --cache .",
|
|
57
51
|
"metadata": "DEBUG=generate node lib/metadata/generate.js",
|
|
58
52
|
"make-docs": "DEBUG=docs node lib/metadata/make_docs.js",
|