html-snapshots 0.18.3 → 1.0.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.
Files changed (84) hide show
  1. package/.eslintrc.json +4 -5
  2. package/.github/workflows/verify.yml +2 -2
  3. package/HISTORY.md +42 -0
  4. package/README.md +109 -256
  5. package/examples/README.md +149 -0
  6. package/examples/custom/myFilter.js +2 -2
  7. package/examples/custom/package-lock.json +2021 -5
  8. package/examples/custom/package.json +1 -1
  9. package/examples/custom/snapshot.js +8 -10
  10. package/examples/debug-phantomjs/package-lock.json +6 -6
  11. package/examples/debug-phantomjs/package.json +1 -1
  12. package/examples/debug-phantomjs/snapshot.js +13 -14
  13. package/examples/debug-puppeteer/README.md +18 -0
  14. package/examples/debug-puppeteer/package-lock.json +2804 -0
  15. package/examples/debug-puppeteer/package.json +12 -0
  16. package/examples/debug-puppeteer/snapshot.js +30 -0
  17. package/examples/html5rocks/package-lock.json +1306 -5
  18. package/examples/html5rocks/package.json +1 -1
  19. package/examples/html5rocks/snapshot.js +14 -23
  20. package/examples/process-limit/package-lock.json +1306 -5
  21. package/examples/process-limit/package.json +1 -1
  22. package/examples/process-limit/snapshot.js +8 -10
  23. package/examples/simple-promise/package-lock.json +1306 -5
  24. package/examples/simple-promise/package.json +1 -1
  25. package/examples/simple-promise/snapshot.js +6 -8
  26. package/examples/sitemap-index/package-lock.json +1306 -5
  27. package/examples/sitemap-index/package.json +1 -1
  28. package/examples/sitemap-index/snapshot.js +6 -6
  29. package/examples/utils/index.js +2 -4
  30. package/examples/verbose/package-lock.json +1306 -5
  31. package/examples/verbose/package.json +1 -1
  32. package/examples/verbose/snapshot.js +12 -14
  33. package/lib/async/index.js +0 -1
  34. package/lib/common/index.js +1 -1
  35. package/lib/common/sitemap/index.js +16 -0
  36. package/lib/common/sitemap/sitemap-collection.js +94 -0
  37. package/lib/common/sitemap/sitemap-index.js +68 -0
  38. package/lib/common/{sitemap.js → sitemap/sitemap.js} +68 -57
  39. package/lib/html-snapshots.js +87 -5
  40. package/lib/input-generators/_base.js +7 -1
  41. package/lib/input-generators/array.js +0 -1
  42. package/lib/input-generators/robots.js +98 -58
  43. package/lib/input-generators/sitemap-index.js +3 -124
  44. package/lib/input-generators/sitemap.js +3 -7
  45. package/lib/input-generators/textfile.js +2 -6
  46. package/lib/puppeteer/index.js +98 -0
  47. package/lib/puppeteer/removeScripts.js +8 -0
  48. package/package.json +9 -4
  49. package/test/helpers/options.js +4 -2
  50. package/test/mocha/browsers/puppeteer.js +106 -0
  51. package/test/mocha/browsers/server/index.html +9 -0
  52. package/test/mocha/browsers/test.js +11 -0
  53. package/test/mocha/html-snapshots/basics.js +60 -47
  54. package/test/mocha/html-snapshots/phantomjs-options.js +54 -60
  55. package/test/mocha/html-snapshots/process-limit.js +126 -120
  56. package/test/mocha/html-snapshots/puppeteer.js +47 -0
  57. package/test/mocha/html-snapshots/robots.js +156 -121
  58. package/test/mocha/html-snapshots/server/public/page-sitemap.xml +16 -0
  59. package/test/mocha/html-snapshots/server/test_robots.txt +13 -0
  60. package/test/mocha/html-snapshots/server/test_robots_sitemap.txt +15 -0
  61. package/test/mocha/html-snapshots/server/test_robots_sitemap.xml +22 -0
  62. package/test/mocha/html-snapshots/sitemap-index.js +100 -90
  63. package/test/mocha/html-snapshots/sitemap.js +75 -31
  64. package/test/mocha/html-snapshots/snapshot-scripts.js +150 -145
  65. package/test/mocha/html-snapshots/test.js +23 -14
  66. package/test/mocha/html-snapshots/test_robots.txt +0 -2
  67. package/test/mocha/html-snapshots/use-jquery.js +65 -64
  68. package/test/mocha/html-snapshots/utils.js +29 -30
  69. package/test/mocha/input-generators/server/test_robots.txt +0 -2
  70. package/test/mocha/input-generators/server/test_robots_bad.txt +0 -2
  71. package/test/mocha/input-generators/server/test_robots_sitemap.txt +17 -0
  72. package/test/mocha/input-generators/server/test_robots_sitemap_bad.txt +17 -0
  73. package/test/mocha/input-generators/server/test_robots_sitemap_multi.txt +18 -0
  74. package/test/mocha/input-generators/server/test_sitemap_index_empty.xml +3 -0
  75. package/test/mocha/input-generators/server/test_sitemap_index_malformed.xml +2 -0
  76. package/test/mocha/input-generators/sitemap-index.js +65 -9
  77. package/test/mocha/input-generators/test.js +276 -280
  78. package/test/mocha/input-generators/test_robots.txt +0 -2
  79. package/test/mocha/input-generators/test_robots_bad.txt +0 -2
  80. package/test/mocha/input-generators/test_robots_sitemap.txt +17 -0
  81. package/test/mocha/input-generators/test_robots_sitemap_bad.txt +17 -0
  82. package/test/mocha/input-generators/test_robots_sitemap_multi.txt +18 -0
  83. package/lib/common/node.js +0 -26
  84. package/test/mocha/common/node.js +0 -100
@@ -1,19 +1,16 @@
1
- /*
1
+ /**
2
2
  * robots.js
3
3
  *
4
4
  * An input generator for html-snapshots that uses a simple robots.txt file.
5
- * Creates the snapshot arguments driven from robots.txt "Allow: ".
6
- * Does not support wildcards.
7
- * If you need wildcards, use the sitemap input generator.
5
+ * Creates the snapshot arguments driven from robots.txt Sitemap or Allow directives.
8
6
  *
9
7
  * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
10
8
  * Licensed under the MIT license.
11
9
  */
12
10
 
13
- const fs = require("fs");
14
- const request = require("request");
11
+ const fs = require("fs").promises;
15
12
  const common = require("../common");
16
- const nodeCall = require("../common/node");
13
+ const { sitemapCollection: smc } = require("../common/sitemap");
17
14
  const base = require("./_base");
18
15
 
19
16
  // The default options
@@ -39,19 +36,81 @@ const robots = {
39
36
  };
40
37
 
41
38
  /**
42
- * Generate input for one line of a simple robots.txt file
43
- * Does not support wildcards.
39
+ * Get all unique `Allow:` url paths from robots.txt.
40
+ * Reads the allow values up to whitespace (end-of-line), or wildcard.
41
+ *
42
+ * @param {String} body - The robots.txt file
43
+ * @returns {Array} collection of allow url paths
44
44
  */
45
- function oneline (line, options) {
46
- const key = "Allow: ",
47
- index = line.indexOf(key);
45
+ function getAllowUrls (body) {
46
+ const allowUrls = new Set();
47
+ const re = /\ballow\b\s*:\s*(?<allowUrl>[^*\s]+)/ig;
48
+ const matches = body.matchAll(re);
49
+ let allowUrl;
48
50
 
49
- if (index !== -1) {
50
- const page = line.substr(index + key.length).replace(/^\s+|\s+$/g, "");
51
- return page.indexOf("*") === -1 && base.input(options, page);
51
+ for (const match of matches) {
52
+ allowUrl = match?.groups?.allowUrl;
53
+ if (allowUrl) {
54
+ allowUrls.add(allowUrl);
55
+ }
52
56
  }
53
57
 
54
- return true;
58
+ return allowUrls.size > 0 ? Array.from(allowUrls) : null;
59
+ }
60
+
61
+ /**
62
+ * Get all the unique sitemap urls from robots.txt.
63
+ *
64
+ * @param {String} body - The robots.txt file
65
+ * @returns {Array} collection of unqiue sitemap urls
66
+ */
67
+ function getSitemapUrls (body) {
68
+ const sitemapUrls = new Set();
69
+ const re = /\bsitemap\b\s*:\s*(?<sitemapUrl>[^\s]+)/ig;
70
+ const matches = body.matchAll(re);
71
+ let sitemapUrl;
72
+
73
+ for (const match of matches) {
74
+ sitemapUrl = match?.groups?.sitemapUrl;
75
+ if (sitemapUrl) {
76
+ sitemapUrls.add(sitemapUrl);
77
+ }
78
+ }
79
+
80
+ return sitemapUrls.size > 0 ? Array.from(sitemapUrls) : null;
81
+ }
82
+
83
+ /**
84
+ * Parse urls from robots.txt and generate input.
85
+ * Look for sitemaps first, then look for allow urls.
86
+ *
87
+ * @param {Object} options - robots.txt options
88
+ * @param {String} body - robots.txt content
89
+ * @returns {Promise} resolves on success
90
+ */
91
+ function processRobotsTxt (options, body) {
92
+ let allowUrls = false;
93
+ const urls = getSitemapUrls(body) || (allowUrls = getAllowUrls(body));
94
+
95
+ if (allowUrls) {
96
+ let result;
97
+ for (const url of urls) {
98
+ result = base.input(options, url);
99
+ // if base.input returns false, then no outputDir was defined,
100
+ // which is a fatal bad option problem and will happen immediately.
101
+ if (!result) {
102
+ throw new Error(
103
+ common.prependMsgToErr(base.generatorError(), url, true)
104
+ );
105
+ }
106
+ }
107
+ return Promise.resolve();
108
+ } else {
109
+ if (urls && urls.length > 0) {
110
+ return smc.process(options, urls);
111
+ }
112
+ throw new Error(`No urls found in ${options.source}`);
113
+ }
55
114
  }
56
115
 
57
116
  /**
@@ -60,54 +119,37 @@ function oneline (line, options) {
60
119
  * @param {Object} options - Robots.txt options
61
120
  * @param {String} options.source - The URL to a robots.txt
62
121
  * @param {Function} options.timeout - Returns the robots.txt request timeout.
63
- * @param {Function} callback - A completion callback.
122
+ * @returns {Promise} resolves to undefined.
64
123
  */
65
- function getRobotsUrl (options, callback) {
66
- request({
124
+ async function getRobotsUrl (options) {
125
+ const { default:got } = await import("got");
126
+ return got({
67
127
  url: options.source,
68
- timeout: options.timeout()
69
- }, (err, res, body) => {
70
- let error = err || common.checkResponse(res, "text/plain");
71
-
128
+ timeout: {
129
+ request: options.timeout()
130
+ }
131
+ }).then(res => {
132
+ const error = common.checkResponse(res, "text/plain");
72
133
  if (error) {
73
- callback(common.prependMsgToErr(error, options.source, true));
74
- } else {
75
- body.toString().split('\n').every(line => {
76
- // Process the line input, but break if base.input returns false.
77
- // For now, this can only happen if no outputDir is defined,
78
- // which is a fatal bad option problem and will happen immediately.
79
- if (!oneline(line, options)) {
80
- error = common.prependMsgToErr(base.generatorError(), line, true);
81
- return false;
82
- }
83
- return true;
84
- });
85
-
86
- callback(error);
134
+ throw new Error(error);
87
135
  }
136
+ return processRobotsTxt(options, res.body.toString());
137
+ }).catch(err => {
138
+ throw new Error(common.prependMsgToErr(err, options.source, true));
88
139
  });
89
140
  }
90
141
 
91
142
  /**
92
143
  * Reads the robots.txt file and parses it.
144
+ *
145
+ * @param {Object} options - Robots.txt options
93
146
  */
94
- function getRobotsFile (options, callback) {
95
- fs.readFile(options.source, (err, data) => {
96
- if (!err) {
97
- data.toString().split('\n').every(line => {
98
- // Process the line input, but break if base.input returns false.
99
- // For now, this can only happen if no outputDir is defined,
100
- // which is a fatal bad option problem and will happen immediately.
101
- if (!oneline(line, options)) {
102
- err = common.prependMsgToErr(base.generatorError(), line, true);
103
- return false;
104
- }
105
- return true;
106
- });
107
- }
108
-
109
- callback(err);
110
- });
147
+ function getRobotsFile (options) {
148
+ return fs.readFile(options.source)
149
+ .then(data => processRobotsTxt(options, data.toString()))
150
+ .catch(err => {
151
+ throw new Error(common.prependMsgToErr(err, options.source, true));
152
+ });
111
153
  }
112
154
 
113
155
  /**
@@ -122,10 +164,8 @@ function getRobotsFile (options, callback) {
122
164
  * @returns {Promise} Resolves to undefined on completion.
123
165
  */
124
166
  function generateInput (options) {
125
- return nodeCall(
126
- common.isUrl(options.source) ? getRobotsUrl : getRobotsFile,
127
- options
128
- )
167
+ const retrieve = common.isUrl(options.source) ? getRobotsUrl : getRobotsFile;
168
+ return retrieve(options)
129
169
  .catch(err => {
130
170
  options._abort(err);
131
171
  })
@@ -7,16 +7,8 @@
7
7
  * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
8
8
  * Licensed under the MIT license.
9
9
  */
10
- /* global Promise */
11
-
12
- const fs = require("fs");
13
- const pathLib = require("path");
14
- const _ = require("lodash");
15
- const mkdirp = require("mkdirp");
16
- const xml2js = require("xml2js");
17
10
  const common = require("../common");
18
- const nodeCall = require("../common/node");
19
- const smLib = require("../common/sitemap");
11
+ const { sitemap: sm } = require("../common/sitemap");
20
12
  const base = require("./_base");
21
13
 
22
14
  // The default options
@@ -44,116 +36,6 @@ const sitemapIndex = {
44
36
  }
45
37
  };
46
38
 
47
- /**
48
- * Make sure path exists so a sitemap write can succeed.
49
- *
50
- * @param {String} outputPath - File path and name.
51
- * @param {Function} callback - Completion callback.
52
- */
53
- function prepareWrite (outputPath, callback) {
54
- const path = pathLib.parse(outputPath);
55
- const dir = pathLib.join(path.root, path.dir);
56
- mkdirp(dir).then(() => callback()).catch(callback);
57
- }
58
-
59
- /**
60
- * Parse a sitemap, generate input, and optionally update the local file
61
- * for sitemapPolicy purposes.
62
- * This writes the sitemap files out EVEN IF sitemapPolicy is false.
63
- * The thinking is that it's better/cheaper to do this b/c if sitemapPolicy is
64
- * enabled later, then there is something to compare against, for more savings.
65
- *
66
- * @param {Object} options - Sitemap options.
67
- * @param {Object} options.__sitemapIndex - Sitemap Index options.
68
- * @param {String} document - xml document string.
69
- * @param {Function} callback - A completion callback.
70
- */
71
- function processSitemap (options, document, callback) {
72
- smLib.parse(options, document, err => {
73
- const sitemapIndexOpts = options.__sitemapIndex;
74
- const outputPath = base.outputFile(sitemapIndexOpts, options.source);
75
-
76
- if (!err && sitemapIndexOpts.sitemapOutputDir) {
77
- prepareWrite(outputPath, err => {
78
- if (!err) {
79
- fs.writeFile(outputPath, document, callback);
80
- } else {
81
- callback(common.prependMsgToErr(err, outputPath, true));
82
- }
83
- });
84
- } else {
85
- callback(err);
86
- }
87
- });
88
- }
89
-
90
- /**
91
- * Parse a sitemap index document
92
- * For each qualifying sitemap element, download, parse the sitemap.
93
- * Stops processing if an error occurs.
94
- *
95
- * @param {Object} options - Sitemap options.
96
- * @param {Boolean} options.sitemapPolicy - True if sitemap policy should be used.
97
- * @param {String} document - xml document string.
98
- * @param {Function} callback - A completion callback.
99
- */
100
- function parse (options, document, callback) {
101
- xml2js.parseString(document, {
102
- trim: true,
103
- normalizeTags: true,
104
- normalize: true
105
- }, (err, result) => {
106
- const sitemapUrls = [];
107
- const sitemapIndexOptions = Object.assign({}, options, {
108
- outputPath: undefined
109
- });
110
-
111
- if (!err) {
112
- // Check if we should process each sitemap in the index.
113
- _.forEach(
114
- // if the sitemap index is malformed, just blow up
115
- result.sitemapindex.sitemap,
116
- sitemapNode => {
117
- // optionally ignore current sitemaps by sitemap policy
118
- const shouldProcess = !options.sitemapPolicy ||
119
- !smLib.stillCurrent(sitemapNode, sitemapIndexOptions);
120
-
121
- if (shouldProcess) {
122
- // if sitemap index is malformed, just blow up
123
- sitemapUrls.push(sitemapNode.loc[0]);
124
- }
125
- }
126
- );
127
-
128
- // Edge case message for clarity
129
- if (sitemapUrls.length === 0) {
130
- console.log("[*] No sitemaps qualified for processing");
131
- }
132
-
133
- // Get all the sitemaps, parse them, and generate input for each
134
- Promise.all(sitemapUrls.map(sitemapUrl => {
135
- const sitemapOptions = Object.assign({}, options, {
136
- source: sitemapUrl,
137
- input: "sitemap",
138
- sitemapOutputDir: false,
139
- __sitemapIndex: sitemapIndexOptions
140
- });
141
-
142
- console.log(`[+] Loading the sitemap: '${sitemapUrl}'`);
143
-
144
- return nodeCall(smLib.getUrl, sitemapOptions, processSitemap);
145
- }))
146
- .then(() => {
147
- // Ignore the array of results to this function
148
- callback();
149
- })
150
- .catch(callback);
151
- } else {
152
- callback(err);
153
- }
154
- });
155
- }
156
-
157
39
  /**
158
40
  * generateInput
159
41
  *
@@ -165,11 +47,8 @@ function parse (options, document, callback) {
165
47
  * @param {String} options.source - Sitemap URL or file path.
166
48
  */
167
49
  function generateInput (options) {
168
- return nodeCall(
169
- common.isUrl(options.source) ? smLib.getUrl : smLib.getFile,
170
- options,
171
- parse
172
- )
50
+ const retrieve = common.isUrl(options.source) ? sm.getUrl : sm.getFile;
51
+ return retrieve(options, sm.parse)
173
52
  .catch(err => {
174
53
  options._abort(err);
175
54
  })
@@ -9,8 +9,7 @@
9
9
  */
10
10
 
11
11
  const common = require("../common");
12
- const nodeCall = require("../common/node");
13
- const smLib = require("../common/sitemap");
12
+ const { sitemap: sm } = require("../common/sitemap");
14
13
  const base = require("./_base");
15
14
 
16
15
  // The default options
@@ -48,11 +47,8 @@ const sitemap = {
48
47
  * @returns {Promise} Resolves to undefined on completion.
49
48
  */
50
49
  function generateInput (options) {
51
- return nodeCall(
52
- common.isUrl(options.source) ? smLib.getUrl : smLib.getFile,
53
- options,
54
- smLib.parse
55
- )
50
+ const retrieve = common.isUrl(options.source) ? sm.getUrl : sm.getFile;
51
+ return retrieve(options, sm.parse)
56
52
  .catch(err => {
57
53
  options._abort(err);
58
54
  })
@@ -8,10 +8,9 @@
8
8
  * Licensed under the MIT license.
9
9
  */
10
10
 
11
- const fs = require("fs");
11
+ const fs = require("fs").promises;
12
12
  const base = require("./_base");
13
13
  const common = require("../common");
14
- const nodeCall = require("../common/node");
15
14
 
16
15
  // The default options
17
16
  const defaults = Object.freeze({
@@ -44,10 +43,7 @@ const textfile = {
44
43
  * @returns {Promise} resolves when complete.
45
44
  */
46
45
  function generateInput (options) {
47
- return nodeCall(
48
- fs.readFile,
49
- options.source
50
- )
46
+ return fs.readFile(options.source)
51
47
  .catch(err => {
52
48
  options._abort(err);
53
49
  })
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env -S node --unhandled-rejections=strict
2
+ /**
3
+ * puppeteer process entry point
4
+ *
5
+ * Command line arguments:
6
+ * 0 - /path/to/node
7
+ * 1 - *this script*
8
+ * 2 - outputFile
9
+ * 3 - url
10
+ * 4 - selector
11
+ * [5] - timeout
12
+ * [6] - filter
13
+ * [7] - debug
14
+ * [8] - slowMo
15
+ */
16
+ const puppeteer = require('puppeteer');
17
+ const path = require('path');
18
+ const { promises: fs } = require('fs');
19
+
20
+ if (process.argv.length < 5) {
21
+ throw Error(`puppeteer script '${
22
+ process.argv[1]
23
+ }' expected these arguments:\n\
24
+ OUTPUTFILE URL SELECTOR [TIMEOUT FILTER DEBUG SLOWMO]`);
25
+ }
26
+
27
+ const options = {
28
+ outputFile: process.argv[2],
29
+ url: process.argv[3],
30
+ selector: process.argv[4],
31
+ timeout: process.argv[5] ? parseInt(process.argv[5], 10) : 10000,
32
+ filter: process.argv[6] && process.argv[6] !== 'false' ? process.argv[6] : null,
33
+ debug: process.argv[7] && process.argv[7].toLowerCase() === 'true' ? true : false,
34
+ slowMo: process.argv[8] ? parseInt(process.argv[8], 10) : 500
35
+ };
36
+ const launchOptions = options.debug ? {
37
+ headless: false,
38
+ slowMo: options.slowMo,
39
+ devtools: true
40
+ } : {};
41
+
42
+ /**
43
+ * Run the snapshot script.
44
+ *
45
+ * @returns {Promise} on completion
46
+ */
47
+ async function run () {
48
+ let timeLeft, browser;
49
+ const start = Date.now();
50
+
51
+ timeLeft = options.timeout - 200; // leave some room for reporting
52
+ if (timeLeft < 0) timeLeft = 0;
53
+
54
+ try {
55
+ const filter =
56
+ options.filter ? require(options.filter) : passthru => passthru;
57
+
58
+ browser = await puppeteer.launch(launchOptions);
59
+ const page = await browser.newPage();
60
+ await page.goto(options.url, {
61
+ timeout: timeLeft
62
+ });
63
+
64
+ timeLeft -= (Date.now() - start);
65
+ if (timeLeft < 0) timeLeft = 0;
66
+
67
+ await fs.mkdir(path.dirname(options.outputFile), {
68
+ recursive: true
69
+ });
70
+
71
+ await page.waitForSelector(options.selector, {
72
+ Visible: true,
73
+ timeout: timeLeft
74
+ });
75
+
76
+ const content = await page.content();
77
+ await fs.writeFile(options.outputFile, filter(content));
78
+ const elapsed = Date.now() - start;
79
+
80
+ await browser.close();
81
+
82
+ return `snapshot for ${options.url} finished in ${elapsed} ms\n\
83
+ written to ${options.outputFile}`;
84
+ } catch (e) {
85
+ const err = new Error(`failed to snapshot ${options.url}`, {
86
+ cause: e
87
+ });
88
+ if (browser) {
89
+ browser.close().finally(() => {
90
+ throw err;
91
+ });
92
+ }
93
+ throw err;
94
+ }
95
+ }
96
+ run().then(console.log).catch(e => {
97
+ throw e;
98
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Remove all script tags from content
3
+ */
4
+ function removeScriptTags (content) {
5
+ return content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
6
+ }
7
+
8
+ module.exports = removeScriptTags;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "html-snapshots",
3
- "version": "0.18.3",
3
+ "version": "1.0.0",
4
4
  "author": {
5
5
  "name": "Alex Grant",
6
6
  "email": "alex@localnerve.com",
7
- "url": "http://localnerve.com"
7
+ "url": "https://www.localnerve.com"
8
8
  },
9
9
  "homepage": "https://github.com/localnerve/html-snapshots",
10
10
  "description": "A selector-based html snapshot tool using PhantomJS that sources sitemap.xml, robots.txt, or arbitrary input",
@@ -22,8 +22,12 @@
22
22
  "test": "mocha test/mocha/**/*.js --exit --recursive --reporter spec",
23
23
  "test:async": "mocha test/mocha/async/test.js --exit --reporter spec",
24
24
  "test:common": "mocha test/mocha/common/*.js --exit --reporter spec",
25
+ "test:browsers": "mocha test/mocha/browsers/test.js --exit --reporter spec",
26
+ "test:browsers:cover": "c8 -- npm run test:browsers",
25
27
  "test:input-generators": "mocha test/mocha/input-generators/*.js --exit --reporter spec",
28
+ "test:input-generators:debug": "mocha --inspect-brk test/mocha/input-generators/*.js --exit --reporter spec",
26
29
  "test:html-snapshots": "mocha test/mocha/html-snapshots/*.js --exit --reporter spec",
30
+ "test:html-snapshots:debug": "mocha --inspect-brk test/mocha/html-snapshots/*.js --exit --reporter spec",
27
31
  "test:cover": "c8 -- npm test",
28
32
  "validate": "npm ls"
29
33
  },
@@ -49,15 +53,16 @@
49
53
  "async": "3.2.4",
50
54
  "async-lock": "1.3.2",
51
55
  "combine-errors": "3.0.3",
56
+ "got": "12.5.0",
52
57
  "lodash": "4.17.21",
53
58
  "mkdirp": "1.0.4",
54
59
  "phantomjs-prebuilt": "2.1.16",
55
- "request": "2.88.2",
60
+ "puppeteer": "^18.0.5",
56
61
  "rimraf": "3.0.2",
57
62
  "xml2js": "0.4.23"
58
63
  },
59
64
  "devDependencies": {
60
- "eslint": "8.21.0",
65
+ "eslint": "8.23.1",
61
66
  "express": "4.18.1",
62
67
  "mocha": "10.0.0",
63
68
  "c8": "7.12.0",
@@ -9,7 +9,7 @@
9
9
  * If a global phantomjs is defined, decorates html-snapshots options to specify that global
10
10
  * In some test environments (travis), local phantomjs will not install if a global is found.
11
11
  */
12
- var assert = require("assert");
12
+ const assert = require("assert");
13
13
 
14
14
  // @0.12.0, on Travis-ci phantomjs2 WILL install local WITH the older global.
15
15
  // So reverting back to decorateLocal only.
@@ -19,7 +19,9 @@ var assert = require("assert");
19
19
  function fillAbort (options) {
20
20
  if (!options._abort) {
21
21
  options._abort = function (err) {
22
- assert.fail("test", "failed", err.toString(), "");
22
+ const msg = `[options._abort] called: ${err.toString()}`;
23
+ console.log(`@@@ ${msg}`);
24
+ assert.fail(msg);
23
25
  }
24
26
  }
25
27
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Tests that target the puppeteer script specifically.
3
+ *
4
+ * Copyright (c) 2013 - 2022, Alex Grant, LocalNerve, contributors
5
+ */
6
+ /* global before, it */
7
+ const assert = require("assert");
8
+ const path = require("path");
9
+ const rimraf = require("rimraf").sync;
10
+ const spawn = require("child_process").spawn;
11
+ const server = require("../../server");
12
+
13
+ const outputDir = path.join(__dirname, "tmp");
14
+ const port = 9340;
15
+
16
+ function puppeteerTests () {
17
+ const snapshotScript = path.resolve(__dirname, '../../../lib/puppeteer/index.js');
18
+ const outputFile = path.join(outputDir, "snapshot.html");
19
+
20
+ /**
21
+ * Launch the puppeteer script as a process.
22
+ *
23
+ * @param {Array} args - array of puppeteer args
24
+ */
25
+ function spawnPuppeteer (args, done) {
26
+ const cp = spawn(
27
+ snapshotScript,
28
+ args
29
+ , { cwd: process.cwd(), stdio: "inherit", detached: true }
30
+ );
31
+
32
+ cp.on("exit", code => {
33
+ if (code !== 0) {
34
+ return done(new Error(`error exit code: ${code}`));
35
+ }
36
+ done();
37
+ });
38
+ }
39
+
40
+ return function () {
41
+ before(function() {
42
+ server.start(path.join(__dirname, "./server"), port);
43
+ });
44
+
45
+ it("should succeed with minimal args", function (done) {
46
+ this.timeout(5000);
47
+
48
+ rimraf(outputDir);
49
+
50
+ spawnPuppeteer([
51
+ outputFile,
52
+ `http://localhost:${port}/`,
53
+ "body"
54
+ ], done);
55
+ });
56
+
57
+ it("should fail if insufficient input args", function (done) {
58
+ rimraf(outputDir);
59
+
60
+ spawnPuppeteer([
61
+ outputFile,
62
+ "http://someurl.domain/path/resource"
63
+ ], e => {
64
+ const msg = "expected process error";
65
+ assert.ok(e, msg);
66
+ done(e ? undefined : new Error(msg));
67
+ });
68
+ });
69
+
70
+ it("should fail with bad url", function (done) {
71
+ rimraf(outputDir);
72
+
73
+ spawnPuppeteer([
74
+ outputFile,
75
+ "http://nonexistent.domain/path/resource",
76
+ "body",
77
+ 1000
78
+ ], e => {
79
+ const msg = "expected process error";
80
+ assert.ok(e, msg);
81
+ done(e ? undefined : new Error(msg));
82
+ });
83
+ });
84
+
85
+ it("should launch debugging", function (done) {
86
+ const timeout = 5000;
87
+ this.timeout(timeout);
88
+
89
+ rimraf(outputDir);
90
+
91
+ spawnPuppeteer([
92
+ outputFile,
93
+ `http://localhost:${port}/`,
94
+ "body",
95
+ timeout - 1000,
96
+ "false",
97
+ "true",
98
+ 10
99
+ ], done);
100
+ });
101
+ };
102
+ }
103
+
104
+ module.exports = {
105
+ testSuite: puppeteerTests
106
+ };
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <body>
4
+ <h1>Hello World</h1>
5
+ <p>
6
+ This is a simple test file, how do you do?
7
+ </p>
8
+ </body>
9
+ </html>