pagean 4.4.2 → 6.0.1

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 (96) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +9 -9
  3. package/docs/upgrade-guide.md +16 -0
  4. package/index.js +7 -4
  5. package/lib/config.js +2 -2
  6. package/lib/default-config.json +41 -39
  7. package/lib/externalFileUtils.js +1 -1
  8. package/lib/linkUtils.js +162 -25
  9. package/lib/logger.js +1 -1
  10. package/lib/report-template.handlebars +224 -209
  11. package/lib/schemaErrors.js +5 -4
  12. package/lib/testUtils.js +1 -1
  13. package/lib/tests.js +14 -14
  14. package/package.json +28 -30
  15. package/schemas/pageanrc.schema.json +199 -191
  16. package/.codeclimate.json +0 -8
  17. package/.depcheckrc.json +0 -3
  18. package/.dockerignore +0 -12
  19. package/.eslintrc.json +0 -19
  20. package/.gitattributes +0 -4
  21. package/.gitlab/gitlab-releaser.json +0 -16
  22. package/.htmlhintrc +0 -25
  23. package/.markdownlint.json +0 -13
  24. package/.pa11yci.json +0 -5
  25. package/.pageanrc.json +0 -55
  26. package/.stylelintrc.json +0 -7
  27. package/CHANGELOG.md +0 -204
  28. package/gitlab.pageanrc.json +0 -16
  29. package/jest.config.json +0 -22
  30. package/static-server.pageanrc.json +0 -50
  31. package/tests/__snapshots__/config.test.js.snap +0 -591
  32. package/tests/__snapshots__/externalFileUtils.test.js.snap +0 -7
  33. package/tests/__snapshots__/index.test.js.snap +0 -30
  34. package/tests/__snapshots__/pagean.test.js.snap +0 -82
  35. package/tests/__snapshots__/pageanrc.test.js.snap +0 -759
  36. package/tests/__snapshots__/reporter.test.js.snap +0 -509
  37. package/tests/config.test.js +0 -264
  38. package/tests/externalFileUtils.test.js +0 -184
  39. package/tests/index.test.js +0 -181
  40. package/tests/linkUtils.test.js +0 -290
  41. package/tests/logger.test.js +0 -324
  42. package/tests/pagean.test.js +0 -85
  43. package/tests/pageanrc.test.js +0 -96
  44. package/tests/reporter.test.js +0 -82
  45. package/tests/schemaErrors.test.js +0 -88
  46. package/tests/test-cases/.htmlhintrc +0 -25
  47. package/tests/test-cases/brokenLinks.html +0 -22
  48. package/tests/test-cases/consoleLog.html +0 -11
  49. package/tests/test-cases/duplicateLinks.html +0 -22
  50. package/tests/test-cases/dynamicContent.html +0 -13
  51. package/tests/test-cases/externalScripts.html +0 -21
  52. package/tests/test-cases/horizontalScrollbar.html +0 -13
  53. package/tests/test-cases/htmlError.html +0 -11
  54. package/tests/test-cases/noExternalScripts.html +0 -12
  55. package/tests/test-cases/notDocumentLinks.html +0 -18
  56. package/tests/test-cases/pagean-results.json +0 -1
  57. package/tests/test-cases/scriptError404.html +0 -9
  58. package/tests/test-cases/slowLoad.html +0 -9
  59. package/tests/test-configs/cli-tests/.pageanrc.json +0 -28
  60. package/tests/test-configs/cli-tests/all-empty.pageanrc.json +0 -10
  61. package/tests/test-configs/cli-tests/all-errors.pageanrc.json +0 -69
  62. package/tests/test-configs/cli-tests/all-fail-cli.pageanrc.json +0 -9
  63. package/tests/test-configs/cli-tests/no-urls.pageanrc.json +0 -7
  64. package/tests/test-configs/cli-tests/pass-fail-cli.pageanrc.json +0 -14
  65. package/tests/test-configs/integration-tests/all-passing-tests.pageanrc.json +0 -63
  66. package/tests/test-configs/integration-tests/broken-links-error.pageanrc.json +0 -22
  67. package/tests/test-configs/integration-tests/console-error-reporter-cli-json.pageanrc.json +0 -20
  68. package/tests/test-configs/integration-tests/console-error-reporter-html.pageanrc.json +0 -19
  69. package/tests/test-configs/integration-tests/console-error.pageanrc.json +0 -16
  70. package/tests/test-configs/integration-tests/console-output.pageanrc.json +0 -16
  71. package/tests/test-configs/integration-tests/external-scripts-error.pageanrc.json +0 -19
  72. package/tests/test-configs/integration-tests/horizontal-scrollbar.pageanrc.json +0 -16
  73. package/tests/test-configs/integration-tests/html-error.pageanrc.json +0 -16
  74. package/tests/test-configs/integration-tests/page-load-time.pageanrc.json +0 -16
  75. package/tests/test-configs/unit-tests/empty-url-values.pageanrc.json +0 -9
  76. package/tests/test-configs/unit-tests/empty-urls.pageanrc.json +0 -3
  77. package/tests/test-configs/unit-tests/empty.pageanrc.json +0 -2
  78. package/tests/test-configs/unit-tests/global-and-test-specific-settings-shorthand.pageanrc.json +0 -30
  79. package/tests/test-configs/unit-tests/global-and-test-specific-settings-test-props.pageanrc.json +0 -42
  80. package/tests/test-configs/unit-tests/global-and-test-specific-settings.pageanrc.json +0 -34
  81. package/tests/test-configs/unit-tests/global-test-settings.pageanrc.json +0 -15
  82. package/tests/test-configs/unit-tests/htmlhintrc-invalid.pageanrc.json +0 -7
  83. package/tests/test-configs/unit-tests/htmlhintrc-valid.pageanrc.json +0 -7
  84. package/tests/test-configs/unit-tests/ignored-links-denormalized.pageanrc.json +0 -18
  85. package/tests/test-configs/unit-tests/invalid.pageanrc.json +0 -0
  86. package/tests/test-configs/unit-tests/no-test-settings.pageanrc.json +0 -6
  87. package/tests/test-configs/unit-tests/project-no-test-settings.pageanrc.json +0 -7
  88. package/tests/test-configs/unit-tests/puppeteer-no-test-settings.pageanrc.json +0 -9
  89. package/tests/test-configs/unit-tests/reporters-empty.pageanrc.json +0 -7
  90. package/tests/test-configs/unit-tests/reporters-invalid-type.pageanrc.json +0 -9
  91. package/tests/test-configs/unit-tests/reporters-not-array.pageanrc.json +0 -6
  92. package/tests/test-configs/unit-tests/reporters-valid.pageanrc.json +0 -9
  93. package/tests/test-configs/unit-tests/test-specific-settings.pageanrc.json +0 -26
  94. package/tests/test-configs/unit-tests/url-types.pageanrc.json +0 -10
  95. package/tests/testUtils.test.js +0 -244
  96. package/tests/tests.test.js +0 -533
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Aaron Goldenthal
3
+ Copyright (c) 2019 - 2021 Aaron Goldenthal
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Pagean is a web page analysis tool designed to automate tests requiring web pages to be loaded in a browser window (e.g. 404 error loading an external resource, page renders with horizontal scrollbars). The specific tests are outlined below, but are all general tests that do not include any page-specific logic.
4
4
 
5
- **Note: As of v4.0.0, this module is being being released as `pagean` and `page-load-tests` has been deprecated. This drove breaking configuration changes that will require updating your project's configuration file. See the [version upgrade guide](https://gitlab.com/gitlab-ci-utils/pagean/-/blob/master/docs/upgrade-guide.md) for complete details.**
6
-
7
5
  ## Installation
8
6
 
9
7
  Install Pagean globally (as shown below), or locally, via [npm](https://www.npmjs.com/).
@@ -121,7 +119,11 @@ Each external script is saved only once, but will be reported on any page where
121
119
  The broken link test checks for broken links on the page. It checks any `<a>` tag on the page with `href` pointing to another location on the current page or another page (i.e. only `http(s)` or `file` protocols).
122
120
 
123
121
  - For links within the page, this test checks for existence of the element on the page, passing if the element exists and failing otherwise (and passing for cases that are always valid, e.g. `#` or `#top` for the current page). It does not check the visibility of the element. Failing tests return a response of "#element Not Found" (where `#element` identifies the specific element).
124
- - For links to other pages, the test makes a `HEAD` request for that URL and checks the response. If an erroneous response is returned (>= 400 with no execution error) and not code 429 (Too Many Requests), the request is retried with a `GET` request. The test test passes for HTTP responses < 400 and fails otherwise (if HTTP response is >= 400 or another error occurs). If the link point to an element on that page, there is no check that the element exists. Some examples have been seen where links that are valid in a browser show an error in this test.
122
+ - For links to other pages, the test tries to most efficiently confirm whether the target link is valid. It first makes a `HEAD` request for that URL and checks the response. If an erroneous response is returned (>= 400 with no execution error) and not code 429 (Too Many Requests), the request is retried with a `GET` request. The test passes for HTTP responses < 400 and fails otherwise (if HTTP response is >= 400 or another error occurs).
123
+ - This can result in false failure indications, specifically for `file://` links (`404` or `ECONNREFUSED`) or where the browser passes a domain identity with the request (page loads when tested, but `401` response for links to that page). For these cases, or other false failures, the test configuration allows a boolean `checkWithBrowser` option that will instead check links by loading the target in the browser (via `puppeteer`). Note this can increase test execution time, in some cases substantially, due to the time to open a new browser tab and plus load the page and all assets.
124
+ - If the link to another page includes a hash it is removed prior to checking. The test in this case is confirming a valid link, not that the element exists, which is only done for the current page.
125
+ - The test configuration allows an `ignoredLinks` array listing link URLs to ignore for this test. Note this only applies to links to other pages, not links within the page, which are always checked.
126
+ - To optimize performance, link test results are cached and those links are not re-tested for the entire test run (across all tested URLs). The test configuration allows a boolean `ignoreDuplicates` option that can be set to `false` to bypass this behavior and re-test all links. The results for any failed links are included in the reports in any case.
125
127
 
126
128
  For any failing test, the `data` array in the test report includes the original URL and the response code or error as shown below.
127
129
 
@@ -142,10 +144,6 @@ For any failing test, the `data` array in the test report includes the original
142
144
  ]
143
145
  ```
144
146
 
145
- The test configuration allows an `ignoredLinks` array listing link URLs to ignore.
146
-
147
- **Note: This test currently fails with warning by default so that it is not a breaking change, but this will be updated in the next major release.**
148
-
149
147
  ## Reports
150
148
 
151
149
  Based on the `reporters` configuration, Pagean results may be displayed in the console and saved in two reports in the project root directory (any or all of the three):
@@ -175,7 +173,7 @@ Below is an example `.pageanrc.json` file, which is broken into six major proper
175
173
  - The shorthand notation allows easy enabling/disabling of tests. In this format the test name is given with a boolean value to enable or disable the test. In this case any other test-specific settings use the default values.
176
174
  - The longhand version includes an object for each test. Every test includes two possible properties (some tests include additional settings):
177
175
  - `enabled`: A boolean value to enable/disable the test, and some tests include additional settings (default `true` for all tests).
178
- - `failWarn`: A boolean value causing a failed test to report a warning instead of failure. A warning result will not cause the test process to fail (exit with an error code). The default value for all tests is `false` except the `externalScriptTest` and `brokenLinkTest`, as shown below.
176
+ - `failWarn`: A boolean value causing a failed test to report a warning instead of failure. A warning result will not cause the test process to fail (exit with an error code). The default value for all tests is `false` except the `externalScriptTest`, as shown below.
179
177
 
180
178
  The shorthand:
181
179
 
@@ -238,7 +236,9 @@ All available settings with the default values are shown below.
238
236
  },
239
237
  "brokenLinkTest": {
240
238
  "enabled": true,
241
- "failWarn": true,
239
+ "failWarn": false,
240
+ "checkWithBrowser": false,
241
+ "ignoreDuplicates": true,
242
242
  "ignoredLinks": []
243
243
  }
244
244
  },
@@ -1,5 +1,21 @@
1
1
  # Version Upgrade Guide
2
2
 
3
+ ## Upgrading from v4.x to v5.0
4
+
5
+ 1. Update to a Node.js current or LTS release (`^12.20.0 || ^14.15.0 || >=16.0.0`). Pagean may still work in other releases, but is not specifically tested or supported.
6
+
7
+ 2. The Broken Link Test default setting was originally to fail with warning, but this was updated to fail (without warning) to be consistent with the other tests. To return to the previous behavior, update your configuration settings with:
8
+
9
+ ```json
10
+ {
11
+ "settings": {
12
+ "brokenLinkTest": {
13
+ "failWarn": true
14
+ }
15
+ }
16
+ }
17
+ ```
18
+
3
19
  ## Upgrading from v3.x (`page-load-tests`) to v4.0 (`pagean`)
4
20
 
5
21
  1. Change the configurations file name from `.pltconfig.json` to `.pageanrc.json`
package/index.js CHANGED
@@ -5,10 +5,12 @@ const puppeteer = require('puppeteer');
5
5
  const testLogger = require('./lib/logger');
6
6
  const testReporter = require('./lib/reporter');
7
7
  const { ...testFunctions } = require('./lib/tests');
8
+ const { createLinkChecker } = require('./lib/linkUtils');
8
9
 
9
10
  // eslint-disable-next-line max-lines-per-function
10
- const executeAllTests = async(config) => {
11
+ const executeAllTests = async (config) => {
11
12
  const logger = testLogger(config);
13
+ const linkChecker = createLinkChecker();
12
14
  const browser = await puppeteer.launch(config.puppeteerLaunchOptions);
13
15
 
14
16
  for (const testUrl of config.urls) {
@@ -21,7 +23,8 @@ const executeAllTests = async(config) => {
21
23
  page.on('console', msg => consoleLog.push({ _type: msg._type, _text: msg._text, _stackTraceLocations: msg._stackTraceLocations }));
22
24
  await page.goto(testUrl.url, { waitUntil: 'load' });
23
25
 
24
- const testContext = { page, consoleLog, urlSettings: { ...testUrl.settings, htmlHintConfig: config.htmlHintConfig }, logger };
26
+ const testContext = { page, consoleLog,
27
+ urlSettings: { ...testUrl.settings, htmlHintConfig: config.htmlHintConfig }, logger, linkChecker };
25
28
  for (const test of Object.keys(testFunctions)) {
26
29
  await testFunctions[test](testContext);
27
30
  }
@@ -34,8 +37,8 @@ const executeAllTests = async(config) => {
34
37
 
35
38
  if (testResults.summary.failed > 0) {
36
39
  // For test harness want process to exit with error code
37
- // eslint-disable-next-line no-process-exit
38
- process.exit(1);
40
+ // eslint-disable-next-line no-process-exit, no-magic-numbers
41
+ process.exit(2);
39
42
  }
40
43
  };
41
44
 
package/lib/config.js CHANGED
@@ -46,7 +46,7 @@ const consolidateTestSettings = (defaultSettings, globalSettings, urlSettings) =
46
46
  const sanitizedUrlSettings = urlSettings ? urlSettings : {};
47
47
  const processedSettings = {};
48
48
  for (const key of Object.keys(defaultSettings)) {
49
- processedSettings[key] = Object.assign({}, defaultSettings[key], sanitizedGlobalSettings[key], sanitizedUrlSettings[key]);
49
+ processedSettings[key] = { ...defaultSettings[key], ...sanitizedGlobalSettings[key], ...sanitizedUrlSettings[key] };
50
50
  }
51
51
  if (processedSettings.brokenLinkTest.ignoredLinks) {
52
52
  processedSettings.brokenLinkTest.ignoredLinks = processedSettings.brokenLinkTest.ignoredLinks.map(link => normalizeLink(link));
@@ -73,7 +73,7 @@ const getHtmlHintConfig = (htmlHintConfigFilename) => {
73
73
  return JSON.parse(fs.readFileSync(htmlHintConfigFilename, 'utf-8'));
74
74
  };
75
75
 
76
- const validateConfigSchema = (config) =>{
76
+ const validateConfigSchema = (config) => {
77
77
  const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../', 'schemas', 'pageanrc.schema.json')));
78
78
  const ajv = new Ajv({ allErrors: true });
79
79
  ajvErrors(ajv);
@@ -1,41 +1,43 @@
1
1
  {
2
- "settings": {
3
- "horizontalScrollbarTest": {
4
- "enabled": true,
5
- "failWarn": false
6
- },
7
- "consoleOutputTest": {
8
- "enabled": true,
9
- "failWarn": false
10
- },
11
- "consoleErrorTest": {
12
- "enabled": true,
13
- "failWarn": false
14
- },
15
- "renderedHtmlTest": {
16
- "enabled": true,
17
- "failWarn": false
18
- },
19
- "pageLoadTimeTest": {
20
- "enabled": true,
21
- "failWarn": false,
22
- "pageLoadTimeThreshold": 2
23
- },
24
- "externalScriptTest": {
25
- "enabled": true,
26
- "failWarn": true
27
- },
28
- "brokenLinkTest": {
29
- "enabled": true,
30
- "failWarn": true
31
- }
2
+ "settings": {
3
+ "horizontalScrollbarTest": {
4
+ "enabled": true,
5
+ "failWarn": false
32
6
  },
33
- "reporters": [
34
- "cli",
35
- "html",
36
- "json"
37
- ],
38
- "urls": [
39
- "ignored, required to validate schema"
40
- ]
41
- }
7
+ "consoleOutputTest": {
8
+ "enabled": true,
9
+ "failWarn": false
10
+ },
11
+ "consoleErrorTest": {
12
+ "enabled": true,
13
+ "failWarn": false
14
+ },
15
+ "renderedHtmlTest": {
16
+ "enabled": true,
17
+ "failWarn": false
18
+ },
19
+ "pageLoadTimeTest": {
20
+ "enabled": true,
21
+ "failWarn": false,
22
+ "pageLoadTimeThreshold": 2
23
+ },
24
+ "externalScriptTest": {
25
+ "enabled": true,
26
+ "failWarn": true
27
+ },
28
+ "brokenLinkTest": {
29
+ "enabled": true,
30
+ "failWarn": false,
31
+ "checkWithBrowser": false,
32
+ "ignoreDuplicates": true
33
+ }
34
+ },
35
+ "reporters": [
36
+ "cli",
37
+ "html",
38
+ "json"
39
+ ],
40
+ "urls": [
41
+ "ignored, required to validate schema"
42
+ ]
43
+ }
@@ -13,7 +13,7 @@ const shouldSaveFile = (script, page) => {
13
13
  return scriptUrl.host !== pageUrl.host;
14
14
  };
15
15
 
16
- const saveExternalScript = async(script) => {
16
+ const saveExternalScript = async (script) => {
17
17
  const result = { url: script };
18
18
  try {
19
19
  const scriptUrl = new URL(script);
package/lib/linkUtils.js CHANGED
@@ -1,10 +1,23 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * @module linkUtils
5
+ */
6
+
7
+ const https = require('https');
3
8
  const axios = require('axios');
4
9
  const normalizeUrl = require('normalize-url');
5
10
 
6
11
  const msPerSec = 1000;
7
12
  const timeoutSeconds = 120;
13
+
14
+ /**
15
+ * Enum for HTTP responses
16
+ *
17
+ * @public
18
+ * @readonly
19
+ * @enum {number}
20
+ */
8
21
  const httpResponse = Object.freeze({
9
22
  continue: 100,
10
23
  ok: 200,
@@ -16,14 +29,59 @@ const httpResponse = Object.freeze({
16
29
 
17
30
  const noRetryResponses = [httpResponse.tooManyRequests];
18
31
 
19
- const normalizeLink = url => normalizeUrl(url, { defaultProtocol: 'https:', stripWWW: false, removeQueryParameters: [] });
32
+ /**
33
+ * Normalizes a URL with https://www.npmjs.com/package/normalize-url
34
+ * using defaults plus the following overrides:
35
+ * 1. Set default protocol to https if protocol-relative
36
+ * 2. Do not remove any querystring parameters
37
+ * 3. Strip hash from URL
38
+ * 4. Do not strip "www." from the URL
39
+ *
40
+ * @public
41
+ * @param {string} url The URL to normalize
42
+ * @returns {string} The normalized URL
43
+ */
44
+ const normalizeLink = url => normalizeUrl(url,
45
+ {
46
+ defaultProtocol: 'https:',
47
+ removeQueryParameters: [],
48
+ stripHash: true,
49
+ stripWWW: false
50
+ });
51
+
52
+
53
+ /**
54
+ * Checks settings to determine if the provided link should be ignored
55
+ *
56
+ * @private
57
+ * @param {Object} settings Test settings object, which may contain an
58
+ * ignoredLinks array
59
+ * @param {string} link The link to check against the ignore list
60
+ * @returns {boolean} True if the link should be ignored, otherwise false
61
+ */
62
+ const ignoreLink = (settings, link) => settings.ignoredLinks && settings.ignoredLinks.includes(link);
20
63
 
21
- // The ignoredLinks values are normalized when settings are processed and does not need to be repeated here.
22
- const ignoreLink = (settings, link) => settings.ignoredLinks && settings.ignoredLinks.includes(normalizeLink(link));
23
64
 
65
+ /**
66
+ * Checks a response to an HTTP request, either a response code or explicit error,
67
+ * to identify any failed responses.
68
+ *
69
+ * @public
70
+ * @param {(string|number)} response The response to an HTTP request to check for failure.
71
+ * @returns {boolean} True if failed, otherwise false
72
+ */
24
73
  const isFailedResponse = response => isNaN(response.status) || response.status >= httpResponse.badRequest;
25
74
 
26
- const checkSamePageLink = async(page, link) => {
75
+
76
+ /**
77
+ * Checks a Puppeteer page for the element specified in the hash of the provided link.
78
+ *
79
+ * @private
80
+ * @param {Object} page A Puppeteer page object
81
+ * @param {string} link The link to check
82
+ * @returns {(string|number)} The link status (HTTP response code or error)
83
+ */
84
+ const checkSamePageLink = async (page, link) => {
27
85
  const selector = link.slice(page.url().length);
28
86
  if (selector === '#' || selector === '#top') {
29
87
  return httpResponse.ok;
@@ -33,14 +91,55 @@ const checkSamePageLink = async(page, link) => {
33
91
  return element ? httpResponse.ok : `${selector} Not Found`;
34
92
  };
35
93
 
36
- // Allow cognitive complexity less than 10
37
- // eslint-disable-next-line sonarjs/cognitive-complexity
38
- const checkExternalPageLink = async(page, link, useGet = false) => {
94
+
95
+ /**
96
+ * Checks the provided link for validity by loading in a Puppeteer page.
97
+ *
98
+ * @private
99
+ * @param {Object} page A Puppeteer page object
100
+ * @param {string} link The link to check
101
+ * @returns {(string|number)} The link status (HTTP response code or error)
102
+ */
103
+ const checkExternalPageLinkBrowser = async (page, link) => {
104
+ let status;
105
+ try {
106
+ const testPage = await page.browser().newPage();
107
+ const response = await testPage.goto(link);
108
+ status = response.status();
109
+ await testPage.close();
110
+ }
111
+ catch (err) {
112
+ // Errors are returned in the format: "ENOTFOUND at https://this.url.does.not.exist/",
113
+ // so extract error only and remove URL
114
+ status = err.message.replace(/^(.*) at .*$/, '$1');
115
+ }
116
+ return status;
117
+ };
118
+
119
+
120
+ /**
121
+ * Checks the provided link for validity by requesting with axios. If useGet if false,
122
+ * a HEAD request is made for efficiency. If useGet is true, a full GET request is made.
123
+ *
124
+ * @private
125
+ * @param {Object} page A Puppeteer page object
126
+ * @param {string} link The link to check
127
+ * @param {boolean} [useGet=false] Used to identify the request method to use (HEAD or GET)
128
+ * @returns {(string|number)} The link status (HTTP response code or error)
129
+ */
130
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- Allow less than 10
131
+ const checkExternalPageLink = async (page, link, useGet = false) => {
39
132
  // Get user-agent from page so axios uses the same value for requesting links
40
133
  const userAgent = await page.evaluate('navigator.userAgent');
41
134
 
42
135
  try {
43
136
  const options = { headers: { 'User-Agent': userAgent }, timeout: timeoutSeconds * msPerSec };
137
+ // Using internal browser property since not exposed
138
+ // eslint-disable-next-line no-underscore-dangle
139
+ if (page.browser()._ignoreHTTPSErrors) {
140
+ const agent = new https.Agent({ rejectUnauthorized: false });
141
+ options.httpsAgent = agent;
142
+ }
44
143
  const httpMethod = useGet ? axios.get : axios.head;
45
144
  const response = await httpMethod(link, options);
46
145
  return response.status;
@@ -59,27 +158,65 @@ const checkExternalPageLink = async(page, link, useGet = false) => {
59
158
  }
60
159
  };
61
160
 
62
- const checkLink = async(context, link) => {
63
- let status = httpResponse.unknownError;
64
- try {
65
- if (ignoreLink(context.testSettings, link)) {
66
- // Set to a unique status so obvious that it's ignored
67
- status = httpResponse.continue;
68
- }
69
- else if (link.startsWith(`${context.page.url()}#`)) {
70
- status = await checkSamePageLink(context.page, link);
71
- }
72
- else {
73
- status = await checkExternalPageLink(context.page, link);
161
+
162
+ /**
163
+ * Factory function returning a linkChecker object with a {@link checkLink}
164
+ * function that caches checked link results.
165
+ *
166
+ * @public
167
+ * @returns {Object}
168
+ */
169
+ // eslint-disable-next-line max-lines-per-function
170
+ const createLinkChecker = () => {
171
+ const checkedLinks = new Map();
172
+
173
+ return {
174
+ /**
175
+ * Checks the provided link for validity using context object for a
176
+ * reference to the Puppeteer page and applicable settings.
177
+ *
178
+ * @public
179
+ * @param {Object} context A Pagean test context object
180
+ * @param {string} link The link to check
181
+ * @returns {(string|number)} The link status (HTTP response code or error)
182
+ */
183
+ // eslint-disable-next-line sonarjs/cognitive-complexity, max-lines-per-function
184
+ checkLink: async (context, link) => {
185
+ let status = httpResponse.unknownError;
186
+ try {
187
+ // Check all page links first since normalize removes the hash
188
+ if (link.startsWith(`${context.page.url()}#`)) {
189
+ return await checkSamePageLink(context.page, link);
190
+ }
191
+
192
+ const normalizedLink = normalizeLink(link);
193
+ if (ignoreLink(context.testSettings, normalizedLink)) {
194
+ // Set to a unique status so obvious that it's ignored
195
+ return httpResponse.continue;
196
+ }
197
+
198
+ if (context.testSettings.ignoreDuplicates && checkedLinks.has(link)) {
199
+ return checkedLinks.get(link);
200
+ }
201
+
202
+ if (context.testSettings.checkWithBrowser) {
203
+ status = await checkExternalPageLinkBrowser(context.page, normalizedLink);
204
+ }
205
+ else {
206
+ status = await checkExternalPageLink(context.page, normalizedLink);
207
+ }
208
+ }
209
+ catch {
210
+ status = httpResponse.unknownError;
211
+ }
212
+
213
+ checkedLinks.set(link, status);
214
+ return status;
74
215
  }
75
- }
76
- catch {
77
- status = httpResponse.unknownError;
78
- }
79
- return status;
216
+ };
80
217
  };
81
218
 
82
- module.exports.checkLink = checkLink;
219
+ module.exports.createLinkChecker = createLinkChecker;
83
220
  module.exports.httpResponse = httpResponse;
84
221
  module.exports.isFailedResponse = isFailedResponse;
85
222
  module.exports.normalizeLink = normalizeLink;
package/lib/logger.js CHANGED
@@ -9,7 +9,7 @@ const testResultSymbols = Object.freeze({
9
9
  warning: ' ‼'
10
10
  });
11
11
 
12
- const nullFunction = () => {};
12
+ const nullFunction = () => { };
13
13
 
14
14
  // eslint-disable-next-line max-lines-per-function
15
15
  module.exports = (config) => {