phantomas 2.0.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 +39 -25
- package/README.md +20 -8
- package/bin/phantomas.js +9 -164
- package/bin/program.js +198 -0
- package/bin/utils.js +16 -0
- package/core/modules/navigationTiming/scope.js +7 -1
- package/core/modules/requestsMonitor/requestsMonitor.js +30 -13
- package/core/scope.js +2 -1
- package/extensions/cookies/cookies.js +1 -2
- package/extensions/filmStrip/filmStrip.js +1 -1
- package/extensions/pageSource/pageSource.js +2 -0
- package/extensions/pageStorage/pageStorage.js +82 -0
- package/extensions/screenshot/screenshot.js +27 -9
- package/extensions/scroll/scroll.js +3 -1
- package/extensions/userAgent/userAgent.js +55 -0
- package/extensions/viewport/viewport.js +1 -1
- package/extensions/waitForSelector/waitForSelector.js +0 -1
- package/hooks/build +3 -0
- package/lib/browser.js +81 -33
- package/lib/index.js +18 -9
- package/lib/loader.js +3 -4
- package/lib/metadata/metadata.json +180 -29
- package/modules/ajaxRequests/scope.js +1 -1
- package/modules/analyzeCss/analyzeCss.js +79 -76
- package/modules/analyzeCss/scope.js +1 -1
- package/modules/blockDomains/blockDomains.js +21 -21
- package/modules/cacheHits/cacheHits.js +6 -3
- package/modules/cpuTasks/cpuTasks.js +22 -0
- 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/domQueries.js +16 -19
- 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 +2 -2
- package/modules/localStorage/scope.js +1 -1
- package/modules/protocols/protocols.js +101 -0
- package/modules/requestsStats/requestsStats.js +1 -1
- package/modules/staticAssets/staticAssets.js +2 -1
- package/modules/windowPerformance/windowPerformance.js +1 -0
- package/package.json +31 -20
- package/lib/fast-stats.js +0 -634
- package/lib/metadata/generate.js +0 -283
- package/lib/metadata/make_docs.js +0 -185
- package/reporters/csv.js +0 -54
- package/reporters/plain.js +0 -173
- package/reporters/statsd.js +0 -82
- package/reporters/tap.js +0 -71
- package/reporters/teamcity.js +0 -73
|
@@ -24,28 +24,31 @@ function lowerCaseHeaders(headers) {
|
|
|
24
24
|
|
|
25
25
|
// parse given URL to get protocol and domain
|
|
26
26
|
function parseEntryUrl(entry) {
|
|
27
|
-
const parseUrl = require("url").parse;
|
|
28
27
|
var parsed;
|
|
29
28
|
|
|
30
29
|
// asset type
|
|
31
30
|
entry.type = "other";
|
|
32
31
|
|
|
33
|
-
if (entry.url.indexOf("data:")
|
|
34
|
-
//
|
|
35
|
-
|
|
32
|
+
if (entry.url.indexOf("data:") === 0) {
|
|
33
|
+
// base64 encoded data
|
|
34
|
+
entry.domain = false;
|
|
35
|
+
entry.protocol = false;
|
|
36
|
+
entry.isBase64 = true;
|
|
37
|
+
} else if (entry.url.indexOf("blob:") === 0) {
|
|
38
|
+
// blob image or video
|
|
39
|
+
entry.domain = false;
|
|
40
|
+
entry.protocol = false;
|
|
41
|
+
entry.isBlob = true;
|
|
42
|
+
} else {
|
|
43
|
+
parsed = new URL(entry.url) || {};
|
|
36
44
|
|
|
37
|
-
entry.protocol = parsed.protocol.replace(":", "");
|
|
45
|
+
entry.protocol = parsed.protocol.replace(":", ""); // e.g. "http:"
|
|
38
46
|
entry.domain = parsed.hostname;
|
|
39
|
-
entry.query = parsed.
|
|
47
|
+
entry.query = parsed.search.substring(1);
|
|
40
48
|
|
|
41
49
|
if (entry.protocol === "https") {
|
|
42
50
|
entry.isSSL = true;
|
|
43
51
|
}
|
|
44
|
-
} else {
|
|
45
|
-
// base64 encoded data
|
|
46
|
-
entry.domain = false;
|
|
47
|
-
entry.protocol = false;
|
|
48
|
-
entry.isBase64 = true;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
return entry;
|
|
@@ -103,6 +106,7 @@ function addContentType(headerValue, entry) {
|
|
|
103
106
|
break;
|
|
104
107
|
|
|
105
108
|
case "video/webm":
|
|
109
|
+
case "video/mp4":
|
|
106
110
|
entry.type = "video";
|
|
107
111
|
entry.isVideo = true;
|
|
108
112
|
break;
|
|
@@ -189,6 +193,11 @@ module.exports = function (phantomas) {
|
|
|
189
193
|
const resId = resp._requestId,
|
|
190
194
|
request = requests[resId];
|
|
191
195
|
|
|
196
|
+
if (resp.fromDiskCache === true) {
|
|
197
|
+
phantomas.log("response from disk cache ignored: %j", resp);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
192
201
|
var entry = {
|
|
193
202
|
id: resId,
|
|
194
203
|
url: resp.url,
|
|
@@ -226,7 +235,7 @@ module.exports = function (phantomas) {
|
|
|
226
235
|
*
|
|
227
236
|
* "Throughout this work, time is measured in milliseconds"
|
|
228
237
|
*/
|
|
229
|
-
if (!entry.isBase64) {
|
|
238
|
+
if (!entry.isBase64 && !entry.isBlob) {
|
|
230
239
|
// resp.timing is empty when handling data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D
|
|
231
240
|
assert(
|
|
232
241
|
typeof resp.timing !== "undefined",
|
|
@@ -314,8 +323,14 @@ module.exports = function (phantomas) {
|
|
|
314
323
|
break;
|
|
315
324
|
}
|
|
316
325
|
|
|
326
|
+
// HTTP and TLS protocols version
|
|
327
|
+
entry.httpVersion = resp.protocol;
|
|
328
|
+
if (resp.securityDetails) {
|
|
329
|
+
entry.tlsVersion = resp.securityDetails.protocol;
|
|
330
|
+
}
|
|
331
|
+
|
|
317
332
|
// requests stats
|
|
318
|
-
if (!entry.isBase64) {
|
|
333
|
+
if (!entry.isBase64 && !entry.isBlob) {
|
|
319
334
|
phantomas.incrMetric("requests");
|
|
320
335
|
phantomas.addOffender("requests", {
|
|
321
336
|
url: entry.url,
|
|
@@ -343,6 +358,8 @@ module.exports = function (phantomas) {
|
|
|
343
358
|
|
|
344
359
|
if (entry.isBase64) {
|
|
345
360
|
phantomas.emit("base64recv", entry, resp); // @desc base64-encoded "response" has been received
|
|
361
|
+
} else if (entry.isBlob) {
|
|
362
|
+
// Do nothing
|
|
346
363
|
} else {
|
|
347
364
|
phantomas.log(
|
|
348
365
|
"recv: HTTP %d <%s> [%s]",
|
package/core/scope.js
CHANGED
|
@@ -54,8 +54,7 @@ module.exports = function (phantomas) {
|
|
|
54
54
|
|
|
55
55
|
phantomas.on("init", async (page) => {
|
|
56
56
|
const url = phantomas.getParam("url"),
|
|
57
|
-
|
|
58
|
-
domain = require("url").parse(url).hostname;
|
|
57
|
+
domain = new URL(url).hostname;
|
|
59
58
|
|
|
60
59
|
// domain field in cookies needs to be set
|
|
61
60
|
// https://github.com/miyakogi/pyppeteer/issues/94#issuecomment-403261859
|
|
@@ -28,7 +28,7 @@ module.exports = function (phantomas) {
|
|
|
28
28
|
.replace(/\/+$/, ""),
|
|
29
29
|
filmStripPrefix = phantomas
|
|
30
30
|
.getParam("film-strip-prefix", "screenshot", "string")
|
|
31
|
-
.replace(/[^a-z0-9
|
|
31
|
+
.replace(/[^a-z0-9-]+/gi, "-");
|
|
32
32
|
|
|
33
33
|
var zoomFactor = phantomas.getParam("film-strip-zoom-factor", 0.5);
|
|
34
34
|
phantomas.setZoom(zoomFactor);
|
|
@@ -33,6 +33,8 @@ module.exports = (phantomas) => {
|
|
|
33
33
|
return new Promise(async (resolve) => {
|
|
34
34
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pageevaluatepagefunction-args
|
|
35
35
|
const bodyHandle = await page.$("body");
|
|
36
|
+
|
|
37
|
+
/* istanbul ignore next */
|
|
36
38
|
const html = await page.evaluate((body) => body.innerHTML, bodyHandle);
|
|
37
39
|
|
|
38
40
|
// phantomas.log(html);
|
|
@@ -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
|
+
};
|
|
@@ -5,16 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = function (phantomas) {
|
|
7
7
|
const workingDirectory = require("process").cwd(),
|
|
8
|
-
param = phantomas.getParam("screenshot")
|
|
8
|
+
param = phantomas.getParam("screenshot"),
|
|
9
|
+
fullPage = phantomas.getParam("fullPageScreenshot");
|
|
9
10
|
var path = "";
|
|
10
11
|
|
|
11
12
|
if (typeof param === "undefined") {
|
|
12
13
|
phantomas.log(
|
|
13
|
-
"Screenshot: to enable screenshot of the
|
|
14
|
+
"Screenshot: to enable screenshot of the page run phantomas with --screenshot option"
|
|
14
15
|
);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
if (fullPage === true) {
|
|
20
|
+
// the full page option is now disabled by default (bug #853)
|
|
21
|
+
phantomas.log(
|
|
22
|
+
"Screenshot: --full-page-screenshot option enabled. Please note that the option can cause layout bugs and lazyloadableImagesUnderTheFold miscount"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
// --screenshot
|
|
19
27
|
if (param === true) {
|
|
20
28
|
// defaults to "2013-12-07T20:15:01.521Z.png"
|
|
@@ -27,7 +35,11 @@ module.exports = function (phantomas) {
|
|
|
27
35
|
path = param;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
// deal with relative paths
|
|
39
|
+
if (path.indexOf("/") !== 0) {
|
|
40
|
+
path = workingDirectory + "/" + path;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
phantomas.log("Screenshot will be saved in %s", path);
|
|
32
44
|
|
|
33
45
|
phantomas.on("beforeClose", (page) => {
|
|
@@ -36,17 +48,23 @@ module.exports = function (phantomas) {
|
|
|
36
48
|
const options = {
|
|
37
49
|
path: path,
|
|
38
50
|
type: "png",
|
|
39
|
-
fullPage: true,
|
|
51
|
+
fullPage: fullPage === true,
|
|
40
52
|
};
|
|
41
53
|
phantomas.log("Will take screenshot, options: %j", options);
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
try {
|
|
56
|
+
// https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagescreenshotoptions
|
|
57
|
+
await page.screenshot(options);
|
|
58
|
+
|
|
59
|
+
phantomas.log("Screenshot stored in %s", path);
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
// let clients know that we stored the page source in a file
|
|
62
|
+
phantomas.emit("screenshot", path);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
phantomas.log("Error while taking the screenshot");
|
|
65
|
+
phantomas.log(err);
|
|
66
|
+
}
|
|
47
67
|
|
|
48
|
-
// let clients know that we stored the page source in a file
|
|
49
|
-
phantomas.emit("screenshot", path);
|
|
50
68
|
resolve();
|
|
51
69
|
});
|
|
52
70
|
});
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pass --scroll as an option in CLI mode
|
|
5
5
|
*/
|
|
6
|
-
/* global document: true, window: true */
|
|
7
6
|
"use strict";
|
|
8
7
|
|
|
9
8
|
module.exports = function (phantomas) {
|
|
@@ -22,7 +21,10 @@ module.exports = function (phantomas) {
|
|
|
22
21
|
return new Promise(async (resolve) => {
|
|
23
22
|
phantomas.log("Scrolling the page...");
|
|
24
23
|
|
|
24
|
+
/* istanbul ignore next */
|
|
25
25
|
await page.evaluate(() => document.body.scrollIntoView(false));
|
|
26
|
+
|
|
27
|
+
/* istanbul ignore next */
|
|
26
28
|
const scrollOffset = await page.evaluate(() => document.body.scrollTop);
|
|
27
29
|
|
|
28
30
|
// wait for lazy loading to do its job
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets a user agent according to --user-agent or --phone or --tablet options
|
|
3
|
+
*/
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
module.exports = function (phantomas) {
|
|
7
|
+
// the user-agent template we use for all emulated devices
|
|
8
|
+
let userAgent =
|
|
9
|
+
"Mozilla/5.0 (<Platform>) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<BrowserVersion> Safari/537.36 Phantomas/<PhantomasVersion>";
|
|
10
|
+
|
|
11
|
+
// default platform for desktop
|
|
12
|
+
let platform = "Windows NT 10.0; Win64; x64";
|
|
13
|
+
|
|
14
|
+
// --phone option overwrites the default platform
|
|
15
|
+
if (
|
|
16
|
+
phantomas.getParam("phone") === true ||
|
|
17
|
+
phantomas.getParam("phone-landscape") === true
|
|
18
|
+
) {
|
|
19
|
+
platform = "Linux; Android 10; SM-G981B";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --tablet option overwrites the default platform
|
|
23
|
+
if (
|
|
24
|
+
phantomas.getParam("tablet") === true ||
|
|
25
|
+
phantomas.getParam("tablet-landscape") === true
|
|
26
|
+
) {
|
|
27
|
+
platform = "Linux; Android 10; SM-T870";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// if --user-agent option is set, it overwrites --phone and --tablet
|
|
31
|
+
// it can contain <Platform>, <BrowserVersion> and <PhantomasVersion> if needed
|
|
32
|
+
const param = phantomas.getParam("user-agent");
|
|
33
|
+
if (typeof param !== "undefined") {
|
|
34
|
+
phantomas.log(
|
|
35
|
+
"userAgent: --user-agent option detected with value %s",
|
|
36
|
+
param
|
|
37
|
+
);
|
|
38
|
+
userAgent = param;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
phantomas.on("init", async (page, browser) => {
|
|
42
|
+
const browserVersion = await browser.version();
|
|
43
|
+
// browserVersion will look like HeadlessChrome/88.0.4298.0
|
|
44
|
+
// let's keep the number only:
|
|
45
|
+
const versionNumber = browserVersion.split("/")[1];
|
|
46
|
+
|
|
47
|
+
userAgent = userAgent.replace("<Platform>", platform);
|
|
48
|
+
userAgent = userAgent.replace("<BrowserVersion>", versionNumber);
|
|
49
|
+
userAgent = userAgent.replace("<PhantomasVersion>", phantomas.getVersion());
|
|
50
|
+
|
|
51
|
+
// @see // https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagesetuseragentuseragent
|
|
52
|
+
await page.setUserAgent(userAgent);
|
|
53
|
+
phantomas.log("userAgent set to %s", userAgent);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provides the --viewport option to set any device resolution and pixel density ratio.
|
|
3
3
|
* If the user sets a viewport size as well as a device option (--phone, --tablet, ...),
|
|
4
|
-
* we assume that he
|
|
4
|
+
* we assume that he or she wants to overwrite the device values.
|
|
5
5
|
*
|
|
6
6
|
* Two syntaxes are supported:
|
|
7
7
|
* - 1200x800 will set a 1x pixel density ratio
|
package/hooks/build
ADDED
package/lib/browser.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Expose puppeteer API and events emitter object for lib/index.js
|
|
3
3
|
*/
|
|
4
4
|
const debug = require("debug")("phantomas:browser"),
|
|
5
|
-
puppeteer = require("puppeteer")
|
|
6
|
-
VERSION = require("../package.json").version;
|
|
5
|
+
puppeteer = require("puppeteer");
|
|
7
6
|
|
|
8
7
|
function Browser() {
|
|
9
8
|
this.browser = null;
|
|
@@ -17,7 +16,7 @@ function Browser() {
|
|
|
17
16
|
Browser.prototype.bind = (events) => (this.events = events);
|
|
18
17
|
|
|
19
18
|
// initialize puppeter instance
|
|
20
|
-
Browser.prototype.init = async () => {
|
|
19
|
+
Browser.prototype.init = async (phantomasOptions) => {
|
|
21
20
|
const networkDebug = require("debug")("phantomas:network"),
|
|
22
21
|
env = require("process").env;
|
|
23
22
|
|
|
@@ -26,10 +25,23 @@ Browser.prototype.init = async () => {
|
|
|
26
25
|
// page.evaluate throw "Protocol error (Runtime.callFunctionOn): Target closed." without the following
|
|
27
26
|
// https://github.com/GoogleChrome/puppeteer/issues/1175#issuecomment-369728215
|
|
28
27
|
"--disable-dev-shm-usage",
|
|
28
|
+
|
|
29
|
+
// enable http/3 support
|
|
30
|
+
// https://www.bram.us/2020/04/08/how-to-enable-http3-in-chrome-firefox-safari/
|
|
31
|
+
"--enable-quic",
|
|
32
|
+
"--quic-version=h3-29",
|
|
29
33
|
],
|
|
30
34
|
};
|
|
31
35
|
|
|
36
|
+
// handle Phantomas options
|
|
37
|
+
//
|
|
38
|
+
// --ignore-ssl-errors ignores SSL errors, such as expired or self-signed certificate errors
|
|
39
|
+
if (phantomasOptions["ignore-ssl-errors"]) {
|
|
40
|
+
options["ignoreHTTPSErrors"] = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
// customize path to Chromium binary
|
|
44
|
+
/* istanbul ignore next */
|
|
33
45
|
if (env["PHANTOMAS_CHROMIUM_EXECUTABLE"]) {
|
|
34
46
|
options.executablePath = env["PHANTOMAS_CHROMIUM_EXECUTABLE"];
|
|
35
47
|
}
|
|
@@ -37,6 +49,7 @@ Browser.prototype.init = async () => {
|
|
|
37
49
|
// detect that we run inside a container
|
|
38
50
|
// @see https://github.com/jessfraz/dockerfiles/issues/65
|
|
39
51
|
// @see https://github.com/Zenika/alpine-chrome#-the-best-with-seccomp
|
|
52
|
+
/* istanbul ignore next */
|
|
40
53
|
if (env["DOCKERIZED"]) {
|
|
41
54
|
debug("Docker environment detected");
|
|
42
55
|
debug(
|
|
@@ -44,13 +57,27 @@ Browser.prototype.init = async () => {
|
|
|
44
57
|
);
|
|
45
58
|
}
|
|
46
59
|
|
|
60
|
+
// detect that we run inside an Amazon Lambda machine
|
|
61
|
+
// (note: the LAMBDA_TASK_ROOT env variable is automatically set by AWS)
|
|
62
|
+
// @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
|
|
63
|
+
/* istanbul ignore next */
|
|
64
|
+
if (env["LAMBDA_TASK_ROOT"]) {
|
|
65
|
+
// Chrome then requires some more flags
|
|
66
|
+
options.args.push(
|
|
67
|
+
"--no-sandbox",
|
|
68
|
+
"--no-zygote",
|
|
69
|
+
"--disable-gpu",
|
|
70
|
+
"--single-process"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
debug("Launching Puppeteer: %j", options);
|
|
48
75
|
|
|
49
76
|
try {
|
|
50
|
-
// https://github.com/
|
|
77
|
+
// https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#puppeteerlaunchoptions
|
|
51
78
|
this.browser = await puppeteer.launch(options);
|
|
52
79
|
this.page = await this.browser.newPage();
|
|
53
|
-
} catch (ex) {
|
|
80
|
+
} catch (ex) /* istanbul ignore next */ {
|
|
54
81
|
debug("Puppeteer failed to launch: %s", ex);
|
|
55
82
|
throw ex;
|
|
56
83
|
}
|
|
@@ -60,13 +87,6 @@ Browser.prototype.init = async () => {
|
|
|
60
87
|
|
|
61
88
|
debug("Using binary from: %s", this.browser.process().spawnfile);
|
|
62
89
|
|
|
63
|
-
// set a custom user agent, e.g. "phantomas/2.0.0-beta (HeadlessChrome/72.0.3617.0)"
|
|
64
|
-
// https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagesetuseragentuseragent
|
|
65
|
-
const browserVersion = await this.browser.version();
|
|
66
|
-
await this.page.setUserAgent(
|
|
67
|
-
"phantomas/" + VERSION + " (" + browserVersion + ")"
|
|
68
|
-
);
|
|
69
|
-
|
|
70
90
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#browserversion
|
|
71
91
|
debug("Original browser: %s", await this.browser.userAgent());
|
|
72
92
|
debug("Viewport: %j", await this.page.viewport());
|
|
@@ -153,8 +173,19 @@ Browser.prototype.init = async () => {
|
|
|
153
173
|
});
|
|
154
174
|
|
|
155
175
|
this.onRequestLoaded = (eventName, data) => {
|
|
156
|
-
var meta = responses[data.requestId]
|
|
157
|
-
|
|
176
|
+
var meta = responses[data.requestId];
|
|
177
|
+
|
|
178
|
+
/* istanbul ignore if */
|
|
179
|
+
if (typeof meta === "undefined") {
|
|
180
|
+
// the browser sometimes looses trace of a request, let's ignore.
|
|
181
|
+
networkDebug(
|
|
182
|
+
"Can't find request id %d in previous requests",
|
|
183
|
+
data.requestId
|
|
184
|
+
);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var response = meta.response;
|
|
158
189
|
|
|
159
190
|
// errorText: 'net::ERR_FAILED' - request is blocked (meta.response will be empty)
|
|
160
191
|
// errorText: 'net::ERR_ABORTED' - HTTP 404
|
|
@@ -181,17 +212,29 @@ Browser.prototype.init = async () => {
|
|
|
181
212
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#cdpsessionsendmethod-params
|
|
182
213
|
response.getContent = (async () => {
|
|
183
214
|
networkDebug("Getting content for #%s", data.requestId);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
215
|
+
let body = null;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const resp = await this.cdp.send("Network.getResponseBody", {
|
|
219
|
+
requestId: data.requestId,
|
|
220
|
+
});
|
|
221
|
+
networkDebug(
|
|
222
|
+
"Content for #%s received (%d bytes)",
|
|
223
|
+
data.requestId,
|
|
224
|
+
resp.body.length
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
body = resp.body;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
// In case the resource was dumped after a redirect
|
|
230
|
+
// https://github.com/puppeteer/puppeteer/issues/2258
|
|
231
|
+
networkDebug(
|
|
232
|
+
"Could not read the content of #%s. It was probably removed from the browser's buffer by a redirect.",
|
|
233
|
+
data.requestId
|
|
234
|
+
);
|
|
235
|
+
}
|
|
192
236
|
|
|
193
|
-
|
|
194
|
-
return resp.body;
|
|
237
|
+
return body;
|
|
195
238
|
}).bind(this);
|
|
196
239
|
|
|
197
240
|
networkDebug(
|
|
@@ -229,9 +272,10 @@ Browser.prototype.init = async () => {
|
|
|
229
272
|
// Fired when data chunk was received over the network
|
|
230
273
|
this.cdp.on("Network.dataReceived", (data) => {
|
|
231
274
|
networkDebug("Network.dataReceived: %j", data);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
275
|
+
if (responses[data.requestId]) {
|
|
276
|
+
responses[data.requestId]._chunks++;
|
|
277
|
+
responses[data.requestId]._dataLength += data.dataLength;
|
|
278
|
+
}
|
|
235
279
|
});
|
|
236
280
|
|
|
237
281
|
return this.page;
|
|
@@ -255,11 +299,12 @@ Browser.prototype.visit = (url, waitUntil, timeout) => {
|
|
|
255
299
|
waitUntil: waitUntil,
|
|
256
300
|
timeout: (timeout || 30) * 1000, // defaults to 30 seconds, provide in miliseconds!
|
|
257
301
|
});
|
|
302
|
+
|
|
303
|
+
debug("URL opened: <%s>", url);
|
|
258
304
|
} catch (ex) {
|
|
259
305
|
debug("Opening URL failed: " + ex);
|
|
260
306
|
return reject(ex);
|
|
261
307
|
}
|
|
262
|
-
debug("URL opened: <%s>", url);
|
|
263
308
|
|
|
264
309
|
// https://github.com/GoogleChrome/puppeteer/issues/1325#issuecomment-382003386
|
|
265
310
|
// bind to this event when getting "Protocol error (Runtime.callFunctionOn): Target closed."
|
|
@@ -271,7 +316,7 @@ Browser.prototype.visit = (url, waitUntil, timeout) => {
|
|
|
271
316
|
debug("Metrics: %s", JSON.stringify(metrics));
|
|
272
317
|
|
|
273
318
|
this.events.emit("metrics", metrics); // @desc Emitted when Chromuim's page.metrics() has been called
|
|
274
|
-
} catch (ex) {
|
|
319
|
+
} catch (ex) /* istanbul ignore next */ {
|
|
275
320
|
debug("Get metrics failed: " + ex);
|
|
276
321
|
return reject(ex);
|
|
277
322
|
}
|
|
@@ -284,17 +329,20 @@ Browser.prototype.close = async () => {
|
|
|
284
329
|
try {
|
|
285
330
|
// Allow the beforeunload event to be executed
|
|
286
331
|
// https://github.com/puppeteer/puppeteer/blob/v1.11.0/docs/api.md#pagecloseoptions
|
|
287
|
-
if (this.page
|
|
332
|
+
if (this.page && !this.page.isClosed())
|
|
333
|
+
await this.page.close({ runBeforeUnload: true });
|
|
288
334
|
|
|
289
335
|
// The page is closed, let's close the browser
|
|
290
|
-
if (this.browser) await this.browser.close();
|
|
291
|
-
} catch (ex) {
|
|
336
|
+
if (this.browser && this.browser.isConnected()) await this.browser.close();
|
|
337
|
+
} catch (ex) /* istanbul ignore next */ {
|
|
292
338
|
debug("An exception was raised in Browser.prototype.close(): " + ex);
|
|
293
|
-
|
|
339
|
+
debug(ex);
|
|
294
340
|
}
|
|
295
341
|
|
|
296
342
|
this.events.emit("close"); // @desc Chromium has been closed
|
|
297
343
|
debug("Browser closed");
|
|
298
344
|
};
|
|
299
345
|
|
|
346
|
+
Browser.prototype.getPuppeteerBrowser = () => this.browser;
|
|
347
|
+
|
|
300
348
|
module.exports = Browser;
|