pagean 10.2.0 → 11.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.
package/lib/config.js CHANGED
@@ -1,22 +1,23 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Functions for managing Pagean config files.
5
3
  *
6
4
  * @module config
7
5
  */
8
6
 
9
- const fs = require('node:fs');
10
- const path = require('node:path');
11
- const Ajv = require('ajv/dist/2019');
12
- const draft7MetaSchema = require('ajv/dist/refs/json-schema-draft-07.json');
13
- const ajvErrors = require('ajv-errors');
14
- const protocolify = require('protocolify');
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+
10
+ import Ajv from 'ajv/dist/2019.js';
11
+ import ajvErrors from 'ajv-errors';
12
+ import protocolify from 'protocolify';
15
13
 
16
- const { normalizeLink } = require('./link-utils');
17
- const { getUrlsFromSitemap } = require('./sitemap');
14
+ import { __dirname, readJson } from './utils.js';
15
+ import { getUrlsFromSitemap } from './sitemap.js';
16
+ import { normalizeLink } from './link-utils.js';
18
17
 
19
- const defaultConfig = require('./default-config.json');
18
+ const defaultConfig = await readJson(
19
+ path.join(__dirname, 'default-config.json')
20
+ );
20
21
  const defaultHtmlHintConfigFilename = './.htmlhintrc';
21
22
 
22
23
  const getUrl = (testUrl) => {
@@ -125,14 +126,13 @@ const validateConfigSchema = (config) => {
125
126
  // All values hardcoded.
126
127
  // nosemgrep: eslint.detect-non-literal-fs-filename
127
128
  fs.readFileSync(
128
- path.join(__dirname, '../', 'schemas', 'pageanrc.schema.json')
129
+ path.join(__dirname, '..', 'schemas', 'pageanrc.schema.json')
129
130
  )
130
131
  );
131
132
  // Allow allErrors to lint the entire config file, although users could
132
133
  // ReDoS themselves.
133
134
  // nosemgrep: ajv-allerrors-true
134
135
  const ajv = new Ajv({ allErrors: true });
135
- ajv.addMetaSchema(draft7MetaSchema);
136
136
  ajvErrors(ajv);
137
137
  const validate = ajv.compile(schema);
138
138
  const isValid = validate(config);
@@ -144,14 +144,24 @@ const getConfigFromFile = (configFileName) =>
144
144
  // nosemgrep: eslint.detect-non-literal-fs-filename
145
145
  JSON.parse(fs.readFileSync(configFileName, 'utf8'));
146
146
 
147
+ /** @typedef {import('puppeteer').LaunchOptions} LaunchOptions */
148
+
149
+ /**
150
+ * @typedef {object} PageanConfig
151
+ * @property {object} htmlHintConfig The htmlhint configuration.
152
+ * @property {string} project The name of the project.
153
+ * @property {LaunchOptions} puppeteerLaunchOptions Options for launching Puppeteer.
154
+ * @property {string[]} reporters The reporters used to output results.
155
+ * @property {object} urls The URLs to analyze with specific settings.
156
+ */
157
+
147
158
  /**
148
159
  * Loads config from file and returns consolidated config with
149
160
  * defaults where values are not specified.
150
161
  *
151
- * @param {string} configFileName Pagean configuration file name.
152
- * @returns {object} Consolidated Pagean configuration.
153
- * @async
154
- * @throws {TypeError} Throws if config file has an invalid schema.
162
+ * @param {string} configFileName Pagean configuration file name.
163
+ * @returns {Promise<PageanConfig>} Consolidated Pagean configuration.
164
+ * @throws {TypeError} Throws if config file has an invalid schema.
155
165
  * @static
156
166
  * @public
157
167
  */
@@ -189,5 +199,5 @@ const lintConfigFile = (configFileName) => {
189
199
  return validateConfigSchema(config);
190
200
  };
191
201
 
192
- module.exports = processConfig;
193
- module.exports.lintConfigFile = lintConfigFile;
202
+ export default processConfig;
203
+ export { lintConfigFile };
@@ -1,6 +1,16 @@
1
1
  {
2
+ "puppeteerLaunchOptions": {
3
+ "headless": "new"
4
+ },
5
+ "reporters": ["cli", "html", "json"],
2
6
  "settings": {
3
- "horizontalScrollbarTest": {
7
+ "brokenLinkTest": {
8
+ "enabled": true,
9
+ "failWarn": false,
10
+ "checkWithBrowser": false,
11
+ "ignoreDuplicates": true
12
+ },
13
+ "consoleErrorTest": {
4
14
  "enabled": true,
5
15
  "failWarn": false
6
16
  },
@@ -8,11 +18,11 @@
8
18
  "enabled": true,
9
19
  "failWarn": false
10
20
  },
11
- "consoleErrorTest": {
21
+ "externalScriptTest": {
12
22
  "enabled": true,
13
- "failWarn": false
23
+ "failWarn": true
14
24
  },
15
- "renderedHtmlTest": {
25
+ "horizontalScrollbarTest": {
16
26
  "enabled": true,
17
27
  "failWarn": false
18
28
  },
@@ -21,20 +31,10 @@
21
31
  "failWarn": false,
22
32
  "pageLoadTimeThreshold": 2
23
33
  },
24
- "externalScriptTest": {
25
- "enabled": true,
26
- "failWarn": true
27
- },
28
- "brokenLinkTest": {
34
+ "renderedHtmlTest": {
29
35
  "enabled": true,
30
- "failWarn": false,
31
- "checkWithBrowser": false,
32
- "ignoreDuplicates": true
36
+ "failWarn": false
33
37
  }
34
38
  },
35
- "puppeteerLaunchOptions": {
36
- "headless": "new"
37
- },
38
- "reporters": ["cli", "html", "json"],
39
39
  "urls": ["ignored, required to validate schema"]
40
40
  }
@@ -1,14 +1,13 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Utilities for checking external JavaScript files.
5
3
  *
6
4
  * @module external-file-utils
7
5
  */
8
- const fs = require('node:fs');
9
- const path = require('node:path');
10
6
 
11
- const axios = require('axios');
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+
10
+ import axios from 'axios';
12
11
 
13
12
  const externalFilePath = 'pagean-external-scripts';
14
13
 
@@ -30,13 +29,21 @@ const shouldSaveFile = (script, page) => {
30
29
  );
31
30
  };
32
31
 
32
+ /**
33
+ * @typedef {object} ExternalScript
34
+ * @property {string} url The original script url.
35
+ * @property {string} [localFile] The path to the downloaded file.
36
+ * @property {string} [message] The error message if the script
37
+ * failed to download.
38
+ */
39
+
33
40
  /**
34
41
  * Loads the JavaScript file from the specified URL and
35
42
  * saves it to disc.
36
43
  *
37
- * @param {string} script The URL of the JavaScript file.
38
- * @returns {object} An object with original script
39
- * URL and local file name.
44
+ * @param {string} script The URL of the JavaScript file.
45
+ * @returns {Promise<ExternalScript>} An object with original script
46
+ * URL and local file name.
40
47
  * @static
41
48
  */
42
49
  const saveExternalScript = async (script) => {
@@ -67,5 +74,4 @@ const saveExternalScript = async (script) => {
67
74
  return result;
68
75
  };
69
76
 
70
- module.exports.saveExternalScript = saveExternalScript;
71
- module.exports.shouldSaveFile = shouldSaveFile;
77
+ export { saveExternalScript, shouldSaveFile };
package/lib/link-utils.js CHANGED
@@ -1,15 +1,14 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Utilities for checking page links.
5
3
  *
6
4
  * @module link-utils
7
5
  */
8
6
 
9
- const https = require('node:https');
10
- const axios = require('axios');
11
- const normalizeUrl = require('normalize-url');
12
- const cssesc = require('cssesc');
7
+ import https from 'node:https';
8
+
9
+ import axios from 'axios';
10
+ import cssesc from 'cssesc';
11
+ import normalizeUrl from 'normalize-url';
13
12
 
14
13
  const msPerSec = 1000;
15
14
  const timeoutSeconds = 120;
@@ -80,12 +79,14 @@ const isFailedResponse = (response) =>
80
79
  Number.isNaN(Number(response.status)) ||
81
80
  response.status >= httpResponse.badRequest;
82
81
 
82
+ /** @typedef {import('puppeteer').Page} Page */
83
+
83
84
  /**
84
85
  * Checks a Puppeteer page for the element specified in the hash of the provided link.
85
86
  *
86
- * @param {object} page A Puppeteer page object.
87
- * @param {string} link The link to check.
88
- * @returns {(string|number)} The link status (HTTP response code or error).
87
+ * @param {Page} page A Puppeteer page object.
88
+ * @param {string} link The link to check.
89
+ * @returns {Promise<(string|number)>} The link status (HTTP response code or error).
89
90
  * @static
90
91
  * @private
91
92
  */
@@ -105,9 +106,9 @@ const checkSamePageLink = async (page, link) => {
105
106
  /**
106
107
  * Checks the provided link for validity by loading in a Puppeteer page.
107
108
  *
108
- * @param {object} page A Puppeteer page object.
109
- * @param {string} link The link to check.
110
- * @returns {(string|number)} The link status (HTTP response code or error).
109
+ * @param {Page} page A Puppeteer page object.
110
+ * @param {string} link The link to check.
111
+ * @returns {Promise<(string|number)>} The link status (HTTP response code or error).
111
112
  * @static
112
113
  * @private
113
114
  */
@@ -130,10 +131,10 @@ const checkExternalPageLinkBrowser = async (page, link) => {
130
131
  * Checks the provided link for validity by requesting with axios. If useGet if false,
131
132
  * a HEAD request is made for efficiency. If useGet is true, a full GET request is made.
132
133
  *
133
- * @param {object} page A Puppeteer page object.
134
- * @param {string} link The link to check.
135
- * @param {boolean} [useGet] Used to identify the request method to use (HEAD or GET).
136
- * @returns {(string|number)} The link status (HTTP response code or error).
134
+ * @param {Page} page A Puppeteer page object.
135
+ * @param {string} link The link to check.
136
+ * @param {boolean} [useGet] Used to identify the request method to use (HEAD or GET).
137
+ * @returns {Promise<(string|number)>} The link status (HTTP response code or error).
137
138
  * @static
138
139
  * @private
139
140
  */
@@ -200,9 +201,9 @@ const createLinkChecker = () => {
200
201
  * reference to the Puppeteer page and applicable settings.
201
202
  *
202
203
  * @instance
203
- * @param {object} context A Pagean test context object.
204
- * @param {string} link The link to check.
205
- * @returns {(string|number)} The link status (HTTP response code or error).
204
+ * @param {object} context A Pagean test context object.
205
+ * @param {string} link The link to check.
206
+ * @returns {Promise<(string|number)>} The link status (HTTP response code or error).
206
207
  * @public
207
208
  */
208
209
  // eslint-disable-next-line sonarjs/cognitive-complexity -- allow < 10
@@ -240,7 +241,4 @@ const createLinkChecker = () => {
240
241
  };
241
242
  };
242
243
 
243
- module.exports.createLinkChecker = createLinkChecker;
244
- module.exports.httpResponse = httpResponse;
245
- module.exports.isFailedResponse = isFailedResponse;
246
- module.exports.normalizeLink = normalizeLink;
244
+ export { createLinkChecker, httpResponse, isFailedResponse, normalizeLink };
package/lib/logger.js CHANGED
@@ -1,32 +1,34 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Creates an instance of a logger object to manage logging functions.
5
3
  *
6
4
  * @module logger
7
5
  */
8
6
 
9
- const consoleLogger = require('ci-logger');
10
- const { reporterTypes } = require('../lib/reporter');
7
+ import consoleLogger from 'ci-logger';
8
+
9
+ import { reporterTypes } from '../lib/reporter.js';
11
10
 
12
11
  const testResultSymbols = Object.freeze({
13
12
  failed: ' ×',
13
+ pageError: ' ×',
14
14
  passed: ' √',
15
15
  warning: ' ‼'
16
16
  });
17
17
 
18
18
  const nullFunction = () => {};
19
19
 
20
+ /** @typedef {import('./lib/config.js').PageanConfig} PageanConfig */
21
+
20
22
  /**
21
23
  * Factory function that creates an instance of a
22
24
  * logger object to manage logging functions.
23
25
  *
24
- * @param {object} config Pagean configuration.
25
- * @returns {object} A logger object.
26
+ * @param {PageanConfig} config Pagean configuration.
27
+ * @returns {object} A logger object.
26
28
  * @static
27
29
  */
28
30
  // eslint-disable-next-line max-lines-per-function -- factory function with state
29
- module.exports = (config) => {
31
+ const factory = (config) => {
30
32
  const cliReporter = {
31
33
  // If cli reporter is enabled, set console log function, otherwise null function
32
34
  log: config.reporters.includes(reporterTypes.cli)
@@ -43,6 +45,7 @@ module.exports = (config) => {
43
45
  results: [],
44
46
  summary: {
45
47
  failed: 0,
48
+ pageError: 0,
46
49
  passed: 0,
47
50
  tests: 0,
48
51
  warning: 0
@@ -67,6 +70,23 @@ module.exports = (config) => {
67
70
  cliReporter.log({ message: `\nTesting URL: ${url}` });
68
71
  };
69
72
 
73
+ /**
74
+ * Logs an error navigating to the URL under test.
75
+ *
76
+ * @instance
77
+ * @param {string} message The page error message to log.
78
+ */
79
+ const logPageError = (message) => {
80
+ currentUrlTests.pageError = message;
81
+ testResults.summary.pageError++;
82
+
83
+ cliReporter.log({
84
+ isResult: true,
85
+ message: `Failed to navigate to URL: ${message}`,
86
+ resultPrefix: testResultSymbols.pageError
87
+ });
88
+ };
89
+
70
90
  /**
71
91
  * Logs test results for the current URL (indicated
72
92
  * by the last call to StartUrlTests).
@@ -90,7 +110,7 @@ module.exports = (config) => {
90
110
 
91
111
  const outputTestSummary = () => {
92
112
  cliReporter.logAlways({
93
- message: `\n Tests: ${testResults.summary.tests}\n Passed: ${testResults.summary.passed}\n Warning: ${testResults.summary.warning}\n Failed: ${testResults.summary.failed}\n`
113
+ message: `\n Tests: ${testResults.summary.tests}\n Passed: ${testResults.summary.passed}\n Warning: ${testResults.summary.warning}\n Failed: ${testResults.summary.failed}\n Page Errors: ${testResults.summary.pageError}\n`
94
114
  });
95
115
  };
96
116
 
@@ -108,7 +128,10 @@ module.exports = (config) => {
108
128
 
109
129
  return {
110
130
  getTestResults,
131
+ logPageError,
111
132
  logTestResults,
112
133
  startUrlTests
113
134
  };
114
135
  };
136
+
137
+ export default factory;
@@ -18,6 +18,8 @@
18
18
  --color-text-failed: #ad000c;
19
19
  --color-background-warning: #fdec96;
20
20
  --color-text-warning: #7e6902;
21
+ --color-background-page-error: #f6d5c0;
22
+ --color-text-page-error: #994715;
21
23
  }
22
24
 
23
25
  html {
@@ -69,7 +71,7 @@
69
71
  display: flex;
70
72
  flex-direction: row;
71
73
  margin-block-end: 2rem;
72
- max-inline-size: 50%;
74
+ max-inline-size: 75%;
73
75
  }
74
76
 
75
77
  .summary {
@@ -109,6 +111,12 @@
109
111
  color: var(--color-text-warning);
110
112
  }
111
113
 
114
+ .page-error {
115
+ background-color: var(--color-background-page-error);
116
+ border: 1px solid var(--color-text-page-error);
117
+ color: var(--color-text-page-error);
118
+ }
119
+
112
120
  .test-results h2 {
113
121
  margin-block: 1rem 0;
114
122
  }
@@ -207,11 +215,20 @@
207
215
  <div class="summary passed">Passed: {{summary.passed}}</div>
208
216
  <div class="summary warning">Warning: {{summary.warning}}</div>
209
217
  <div class="summary failed">Failed: {{summary.failed}}</div>
218
+ <div class="summary page-error">Page Errors: {{summary.pageError}}</div>
210
219
  </section>
211
220
  <section class="test-results">
212
221
  {{#results}}
213
222
  <h2>URL: {{url}}</h2>
214
223
  <ul class="tests">
224
+ {{#if pageError}}
225
+ <li class="test page-error">
226
+ <div class="header">
227
+ <div class="name">{{pageError}}</div>
228
+ <div class="result">page error</div>
229
+ </div>
230
+ </li>
231
+ {{/if}}
215
232
  {{#tests}}
216
233
  <li class="test {{result}}">
217
234
  {{#if data}}
package/lib/reporter.js CHANGED
@@ -1,13 +1,15 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Manages Pagean reporting.
5
3
  *
6
4
  * @module reporter
7
5
  */
8
- const fs = require('node:fs');
9
- const path = require('node:path');
10
- const handlebars = require('handlebars');
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+
10
+ import handlebars from 'handlebars';
11
+
12
+ import { __dirname } from './utils.js';
11
13
 
12
14
  const htmlReportTemplateName = 'report-template.handlebars';
13
15
  const htmlReportFileName = './pagean-results.html';
@@ -56,5 +58,4 @@ const saveReports = (results, reporters) => {
56
58
  }
57
59
  };
58
60
 
59
- module.exports.saveReports = saveReports;
60
- module.exports.reporterTypes = reporterTypes;
61
+ export { saveReports, reporterTypes };
@@ -1,11 +1,10 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Generates formatted Pagean config schema errors for output to stdout.
5
3
  *
6
4
  * @module schema-errors
7
5
  */
8
- const { red } = require('kleur');
6
+
7
+ import kleur from 'kleur';
9
8
 
10
9
  const getDataKey = (instancePath) => {
11
10
  const baseKey = '<pageanrc>';
@@ -65,10 +64,10 @@ const formatErrors = (errors) => {
65
64
  }
66
65
  return errors.map(
67
66
  (error) =>
68
- ` ${error.dataKey.padEnd(maxLength + margin)}${red(
67
+ ` ${error.dataKey.padEnd(maxLength + margin)}${kleur.red(
69
68
  error.formattedMessage
70
69
  )}`
71
70
  );
72
71
  };
73
72
 
74
- module.exports.formatErrors = formatErrors;
73
+ export { formatErrors };
package/lib/sitemap.js CHANGED
@@ -1,16 +1,20 @@
1
- 'use strict';
1
+ /**
2
+ * Process sitemap files.
3
+ *
4
+ * @module sitemap
5
+ */
6
+
7
+ import fs from 'node:fs';
2
8
 
3
- const fs = require('node:fs');
4
- const axios = require('axios');
5
- const { parseStringPromise } = require('xml2js');
9
+ import axios from 'axios';
10
+ import { parseStringPromise } from 'xml2js';
6
11
 
7
12
  /**
8
13
  * Gets a sitemap, via file path or URL, and returns the string contents.
9
14
  *
10
- * @param {string} url The URL or file path to the sitemap.
11
- * @returns {string} The string contents of the sitemap.
12
- * @async
13
- * @throws {Error} Throws if the sitemap cannot be retrieved.
15
+ * @param {string} url The URL or file path to the sitemap.
16
+ * @returns {Promise<string>} The string contents of the sitemap.
17
+ * @throws {Error} Throws if the sitemap cannot be retrieved.
14
18
  * @static
15
19
  * @private
16
20
  */
@@ -31,9 +35,8 @@ const getSitemap = async (url) => {
31
35
  /**
32
36
  * Parses a sitemap string and returns an array of URLs.
33
37
  *
34
- * @param {string} sitemapXml The string contents of the sitemap.
35
- * @returns {string[]} The URLs from the sitemap.
36
- * @async
38
+ * @param {string} sitemapXml The string contents of the sitemap.
39
+ * @returns {Promise<string[]>} The URLs from the sitemap.
37
40
  * @static
38
41
  * @private
39
42
  */
@@ -75,7 +78,6 @@ const removeExcludedUrls = (urls, exclude = []) => {
75
78
  * @param {string} [find] The string to find in the URLs.
76
79
  * @param {string} [replace] The string to replace in the URLs.
77
80
  * @returns {string[]} The processed URLs.
78
- * @async
79
81
  * @static
80
82
  * @private
81
83
  */
@@ -86,20 +88,23 @@ const replaceUrlStrings = (urls, find, replace) => {
86
88
  return urls.map((url) => url.replace(find, replace));
87
89
  };
88
90
 
91
+ /**
92
+ * @typedef {object} SitemapOptions
93
+ * @property {string} url The URL or file path to the sitemap.
94
+ * @property {string} [find] The string to find in the URLs.
95
+ * @property {string} [replace] The string to replace in the URLs.
96
+ * @property {string[]} [exclude] The URLs to exclude (regular expressions).
97
+ */
98
+
89
99
  /**
90
100
  * Retrieves a list of URLs from a sitemap, via a local file path or URL.
91
101
  * Options are available to exclude URLs from the results, and to find and
92
102
  * replace strings in the URLs (primarily intended for isolated testing,
93
103
  * for example change "https://somewhere.test/" to "http://localhost:3000/").
94
104
  *
95
- * @param {object} options The sitemap processing options.
96
- * @param {string} options.url The URL or file path to the sitemap.
97
- * @param {string} [options.find] The string to find in the URLs.
98
- * @param {string} [options.replace] The string to replace in the URLs.
99
- * @param {string[]} [options.exclude] The URLs to exclude (regular expressions).
100
- * @returns {string[]} The processed URLs from the sitemap.
101
- * @async
102
- * @throws {Error} Throws if the sitemap cannot be processed.
105
+ * @param {SitemapOptions} options The sitemap processing options.
106
+ * @returns {Promise<string[]>} The processed URLs from the sitemap.
107
+ * @throws {Error} Throws if the sitemap cannot be processed.
103
108
  * @static
104
109
  * @public
105
110
  */
@@ -117,4 +122,4 @@ const getUrlsFromSitemap = async ({ url, find, replace, exclude } = {}) => {
117
122
  }
118
123
  };
119
124
 
120
- module.exports.getUrlsFromSitemap = getUrlsFromSitemap;
125
+ export { getUrlsFromSitemap };
package/lib/test-utils.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Miscellaneous test utilities.
5
3
  *
@@ -26,11 +24,12 @@ const getTestSettings = (testSettingProperty, urlSettings) => {
26
24
  * test-specific settings from configuration, passing the test context,
27
25
  * and logging results.
28
26
  *
29
- * @param {string} name The name of the test being run.
30
- * @param {Function} testFunction The test function to be executed.
31
- * @param {object} testContext The test execution context.
32
- * @param {string} testSettingProperty The name of the config property
33
- * with settings for the current test.
27
+ * @param {string} name The name of the test being run.
28
+ * @param {Function} testFunction The test function to be executed.
29
+ * @param {object} testContext The test execution context.
30
+ * @param {string} testSettingProperty The name of the config property
31
+ * with settings for the current test.
32
+ * @returns {Promise<void>}
34
33
  * @static
35
34
  */
36
35
  const pageanTest = async (
@@ -65,5 +64,4 @@ const pageanTest = async (
65
64
  }
66
65
  };
67
66
 
68
- module.exports.testResultStates = testResultStates;
69
- module.exports.pageanTest = pageanTest;
67
+ export { testResultStates, pageanTest };