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 CHANGED
@@ -1,26 +1,30 @@
1
1
  # https://hub.docker.com/_/node
2
- FROM node:lts-alpine3.13
3
-
4
- # Installs latest Chromium package.
5
- # https://pkgs.alpinelinux.org/package/edge/community/x86_64/chromium
6
- ENV CHROMIUM_VERSION 91.0.4472.101-r0
7
-
8
- RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/main" > /etc/apk/repositories \
9
- && echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
10
- && echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
11
- && echo "http://dl-cdn.alpinelinux.org/alpine/v3.13/main" >> /etc/apk/repositories \
12
- && apk upgrade -U -a \
13
- && apk add \
14
- chromium \
15
- ca-certificates \
16
- freetype \
17
- freetype-dev \
18
- harfbuzz \
19
- nss \
20
- ttf-freefont
21
-
22
- RUN which chromium-browser
23
- RUN chromium-browser --no-sandbox --version
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
- ARG COMMIT_SHA="dev"
46
- ENV COMMIT_SHA ${COMMIT_SHA}
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
@@ -10,7 +10,7 @@ phantomas
10
10
 
11
11
  ## Requirements
12
12
 
13
- * [NodeJS](http://nodejs.org) 12+
13
+ * [NodeJS](http://nodejs.org) 14+
14
14
 
15
15
  ## Installation
16
16
 
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
@@ -1,3 +1,3 @@
1
1
  #!/bin/bash
2
2
  # see https://docs.docker.com/docker-hub/builds/advanced/#build-hook-examples
3
- docker build --build-arg COMMIT_SHA=$SOURCE_COMMIT -f $DOCKERFILE_PATH -t $IMAGE_NAME .
3
+ docker build --build-arg GITHUB_SHA=$SOURCE_COMMIT -f $DOCKERFILE_PATH -t $IMAGE_NAME .
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(100); // MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
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, callback) {
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
- new analyzer(css, options, function (err, results) {
99
- var offenderSrc = context || "[inline CSS]";
100
-
101
- if (err !== null) {
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)";
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
- phantomas.incrMetric("cssParsingErrors");
124
- phantomas.addOffender("cssParsingErrors", offender);
123
+ phantomas.incrMetric("cssParsingErrors");
124
+ phantomas.addOffender("cssParsingErrors", offender);
125
+ return;
126
+ }
125
127
 
126
- callback();
127
- return;
128
- }
128
+ var offenderSrc = context || "[inline CSS]";
129
129
 
130
- phantomas.log(
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
- var metrics = results.metrics || {},
137
- offenders = results.offenders || {};
132
+ const metrics = results.metrics,
133
+ offenders = results.offenders;
138
134
 
139
- Object.keys(metrics).forEach(function (metric) {
140
- var metricPrefixed = "css" + ucfirst(metric);
135
+ Object.keys(metrics).forEach((metric) => {
136
+ var metricPrefixed = "css" + ucfirst(metric);
141
137
 
142
- if (/Avg$/.test(metricPrefixed)) {
143
- // update the average value (see #641)
144
- phantomas.addToAvgMetric(metricPrefixed, metrics[metric]);
145
- } else {
146
- // increase metrics
147
- phantomas.incrMetric(metricPrefixed, metrics[metric]);
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
- // and add offenders
151
- if (typeof offenders[metric] !== "undefined") {
152
- offenders[metric].forEach(function (msg) {
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: msg,
175
+ value: metrics[metric],
156
176
  });
157
- });
177
+ break;
158
178
  }
159
- // add more offenders (#578)
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
- var promises = [];
197
+ const promises = [];
199
198
 
200
199
  stylesheets.forEach((entry) => {
201
200
  promises.push(
202
201
  new Promise(async (resolve) => {
203
- var css = entry.inline;
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, resolve);
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.0",
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": ">=12.0"
27
+ "node": ">=14.0"
28
28
  },
29
29
  "dependencies": {
30
- "analyze-css": "^1.0.0",
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
- "node-statsd": "0.1.1",
42
- "puppeteer": "^10.0.0"
36
+ "puppeteer": "^13.0.0"
43
37
  },
44
38
  "devDependencies": {
45
- "eslint": "^7.21.0",
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
- "mockery": "^2.0.0",
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",