From de01f2bf6811b9c425dd4cc1082593f03a154003 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:25:20 +0000 Subject: [PATCH 1/9] Upgrade to `puppeteer@18` for axe updates Switching away from the deprecated `axe-puppeteer` in future unblocks `puppeteer` upgrades: ``` npm ERR! Could not resolve dependency: npm ERR! peer puppeteer@">=1.10.0 <= 18" from @axe-core/puppeteer@4.5.2 ``` --- package-lock.json | 1075 ++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 759 insertions(+), 318 deletions(-) diff --git a/package-lock.json b/package-lock.json index 119ba3ac4d..5dc494ca3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "metalsmith-rollup": "^2.0.0", "metalsmith-uglify": "^2.4.1", "nunjucks": "^3.2.2", - "puppeteer": "^1.14.0", + "puppeteer": "^18.2.1", "rollup-plugin-commonjs": "^9.3.4", "rollup-plugin-node-resolve": "^4.2.4", "sitemap": "^2.2.0", @@ -2696,6 +2696,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/a-sync-waterfall": { "version": "1.0.1", "dev": true, @@ -2794,16 +2804,40 @@ } }, "node_modules/agent-base": { - "version": "4.3.0", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { - "es6-promisify": "^5.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">= 4.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2997,11 +3031,6 @@ "node": ">=0.8.0" } }, - "node_modules/async-limiter": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3241,6 +3270,26 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -3262,6 +3311,37 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/boolbase": { "version": "1.0.0", "dev": true, @@ -3480,10 +3560,35 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } @@ -3665,6 +3770,12 @@ "node": ">=8.10.0" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/ci-info": { "version": "3.2.0", "dev": true, @@ -3830,20 +3941,6 @@ "version": "0.0.1", "license": "MIT" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -3946,6 +4043,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", @@ -4210,6 +4316,12 @@ "node": ">= 0.8.0" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1045489", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", + "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", + "dev": true + }, "node_modules/diff-sequences": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", @@ -4371,6 +4483,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", @@ -4579,19 +4700,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "dev": true, - "license": "MIT" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -5664,19 +5772,63 @@ } }, "node_modules/extract-zip": { - "version": "1.7.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", + "debug": "^4.1.1", + "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "bin": { "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5739,8 +5891,9 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, - "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -5774,21 +5927,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-entry-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fill-range": { "version": "7.0.1", "license": "MIT", @@ -6101,6 +6239,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -6597,18 +6741,6 @@ "node": ">= 6" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/http-proxy-agent/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -6633,29 +6765,40 @@ "dev": true }, "node_modules/https-proxy-agent": { - "version": "2.2.4", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">= 4.5.0" + "node": ">= 6" } }, "node_modules/https-proxy-agent/node_modules/debug": { - "version": "3.2.6", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/https-proxy-agent/node_modules/ms": { "version": "2.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/human-signals": { "version": "2.1.0", @@ -6677,6 +6820,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/iframe-resizer": { "version": "3.6.6", "dev": true, @@ -10846,54 +11009,6 @@ } } }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/jsdom/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/jsesc": { "version": "2.5.2", "dev": true, @@ -11712,20 +11827,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/metalsmith/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/metalsmith/node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -11862,6 +11963,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/modernizr": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.12.0.tgz", @@ -12017,6 +12124,48 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12435,8 +12584,9 @@ }, "node_modules/pend": { "version": "1.2.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true }, "node_modules/picocolors": { "version": "1.0.0", @@ -12713,9 +12863,10 @@ } }, "node_modules/progress": { - "version": "2.0.1", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -12765,9 +12916,10 @@ "dev": true }, "node_modules/proxy-from-env": { - "version": "1.0.0", - "dev": true, - "license": "MIT" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/pseudomap": { "version": "1.0.2", @@ -12780,6 +12932,16 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -12790,54 +12952,84 @@ } }, "node_modules/puppeteer": { - "version": "1.14.0", + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-18.2.1.tgz", + "integrity": "sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ==", "dev": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "debug": "^4.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^2.2.1", - "mime": "^2.0.3", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "18.2.1" }, "engines": { - "node": ">=6.4.0" + "node": ">=14.1.0" } }, - "node_modules/puppeteer/node_modules/debug": { - "version": "4.1.1", + "node_modules/puppeteer-core": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-18.2.1.tgz", + "integrity": "sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1045489", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.9.0" + }, + "engines": { + "node": ">=14.1.0" } }, - "node_modules/puppeteer/node_modules/mime": { - "version": "2.4.0", + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "ms": "2.1.2" }, "engines": { - "node": ">=4.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/puppeteer/node_modules/ms": { - "version": "2.1.1", - "dev": true, - "license": "MIT" + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/puppeteer/node_modules/ws": { - "version": "6.2.2", + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "dev": true, - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/qs": { @@ -13166,14 +13358,17 @@ } }, "node_modules/rimraf": { - "version": "2.6.2", - "dev": true, - "license": "ISC", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dependencies": { - "glob": "^7.0.5" + "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rollup": { @@ -14605,6 +14800,48 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/terser": { "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", @@ -14651,6 +14888,12 @@ "dlv": "^1.1.3" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/tiny-emitter": { "version": "2.1.0", "license": "MIT" @@ -14801,11 +15044,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", @@ -14870,6 +15108,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/universalify": { "version": "0.1.1", "dev": true, @@ -15439,8 +15687,9 @@ }, "node_modules/yauzl": { "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, - "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -17474,6 +17723,16 @@ "version": "20.2.1", "dev": true }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "a-sync-waterfall": { "version": "1.0.1", "dev": true @@ -17550,10 +17809,29 @@ "dev": true }, "agent-base": { - "version": "4.3.0", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "ajv": { @@ -17687,10 +17965,6 @@ "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true }, - "async-limiter": { - "version": "1.0.0", - "dev": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -17856,6 +18130,12 @@ "balanced-match": { "version": "1.0.0" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, "base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -17869,6 +18149,36 @@ "binary-extensions": { "version": "2.2.0" }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "boolbase": { "version": "1.0.0", "dev": true @@ -18042,8 +18352,20 @@ "node-int64": "^0.4.0" } }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "buffer-crc32": { "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true }, "buffer-from": { @@ -18169,6 +18491,12 @@ } } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "ci-info": { "version": "3.2.0", "dev": true @@ -18297,16 +18625,6 @@ "concat-map": { "version": "0.0.1" }, - "concat-stream": { - "version": "1.6.2", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -18388,6 +18706,15 @@ } } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "requires": { @@ -18574,6 +18901,12 @@ "version": "1.0.1", "dev": true }, + "devtools-protocol": { + "version": "0.0.1045489", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", + "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", + "dev": true + }, "diff-sequences": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", @@ -18697,6 +19030,15 @@ "encodeurl": { "version": "1.0.2" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "engine.io": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", @@ -18845,17 +19187,6 @@ "is-symbol": "^1.0.2" } }, - "es6-promise": { - "version": "4.2.8", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, "escalade": { "version": "3.1.1" }, @@ -19600,13 +19931,41 @@ } }, "extract-zip": { - "version": "1.7.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "fast-deep-equal": { @@ -19664,6 +20023,8 @@ }, "fd-slicer": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "requires": { "pend": "~1.2.0" @@ -19690,15 +20051,6 @@ "flatted": "^3.1.0", "rimraf": "^3.0.2" } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } } } }, @@ -19909,6 +20261,12 @@ "fresh": { "version": "0.5.2" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -20253,15 +20611,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -20280,22 +20629,28 @@ } }, "https-proxy-agent": { - "version": "2.2.4", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "6", + "debug": "4" }, "dependencies": { "debug": { - "version": "3.2.6", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -20313,6 +20668,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, "iframe-resizer": { "version": "3.6.6", "dev": true @@ -23451,42 +23812,6 @@ "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "jsesc": { @@ -24002,14 +24327,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, "stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -24225,6 +24542,12 @@ "minimist": "^1.2.5" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "modernizr": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.12.0.tgz", @@ -24338,6 +24661,39 @@ "version": "0.6.2", "dev": true }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -24629,6 +24985,8 @@ }, "pend": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, "picocolors": { @@ -24822,7 +25180,9 @@ } }, "progress": { - "version": "2.0.1", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { @@ -24868,7 +25228,9 @@ "dev": true }, "proxy-from-env": { - "version": "1.0.0", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, "pseudomap": { @@ -24881,6 +25243,16 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -24888,40 +25260,56 @@ "dev": true }, "puppeteer": { - "version": "1.14.0", + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-18.2.1.tgz", + "integrity": "sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ==", "dev": true, "requires": { - "debug": "^4.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^2.2.1", - "mime": "^2.0.3", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "18.2.1" + } + }, + "puppeteer-core": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-18.2.1.tgz", + "integrity": "sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw==", + "dev": true, + "requires": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1045489", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.9.0" }, "dependencies": { "debug": { - "version": "4.1.1", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "mime": { - "version": "2.4.0", - "dev": true - }, "ms": { - "version": "2.1.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "ws": { - "version": "6.2.2", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "requires": {} } } }, @@ -25160,10 +25548,11 @@ "dev": true }, "rimraf": { - "version": "2.6.2", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "rollup": { @@ -26236,6 +26625,44 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "terser": { "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", @@ -26271,6 +26698,12 @@ "dlv": "^1.1.3" } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "tiny-emitter": { "version": "2.1.0" }, @@ -26381,10 +26814,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, - "typedarray": { - "version": "0.0.6", - "dev": true - }, "typescript": { "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", @@ -26420,6 +26849,16 @@ "which-boxed-primitive": "^1.0.2" } }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "universalify": { "version": "0.1.1", "dev": true @@ -26838,6 +27277,8 @@ }, "yauzl": { "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "requires": { "buffer-crc32": "~0.2.3", diff --git a/package.json b/package.json index 5605e4b86c..b735efb17c 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "metalsmith-rollup": "^2.0.0", "metalsmith-uglify": "^2.4.1", "nunjucks": "^3.2.2", - "puppeteer": "^1.14.0", + "puppeteer": "^18.2.1", "rollup-plugin-commonjs": "^9.3.4", "rollup-plugin-node-resolve": "^4.2.4", "sitemap": "^2.2.0", From a0f6ed1f09004afa1b397e8673710b5cf8eb8fe2 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 12 Jan 2023 09:31:31 +0000 Subject: [PATCH 2/9] Use `@axe-core/puppeteer` to replace `axe-puppeteer` (deprecated) Additionally adds `jest-axe` for violation reporting --- ...dit.test.js => accessiblity-audit.test.js} | 37 +- jest.setup.js | 4 + package-lock.json | 319 +++++++++++++++--- package.json | 3 +- 4 files changed, 301 insertions(+), 62 deletions(-) rename __tests__/{accessiblity_audit.test.js => accessiblity-audit.test.js} (62%) diff --git a/__tests__/accessiblity_audit.test.js b/__tests__/accessiblity-audit.test.js similarity index 62% rename from __tests__/accessiblity_audit.test.js rename to __tests__/accessiblity-audit.test.js index dda95e1786..32d668e923 100644 --- a/__tests__/accessiblity_audit.test.js +++ b/__tests__/accessiblity-audit.test.js @@ -1,5 +1,5 @@ -const { AxePuppeteer } = require('axe-puppeteer') +const { AxePuppeteer } = require('@axe-core/puppeteer') const { setupPage } = require('../lib/jest-utilities.js') const configPaths = require('../lib/paths.js') @@ -8,7 +8,11 @@ const PORT = configPaths.testPort let page const baseUrl = 'http://localhost:' + PORT -async function audit (page) { +async function analyze (path) { + const { href } = new URL(path, baseUrl) + + await page.goto(href, { waitUntil: 'load' }) + const axe = new AxePuppeteer(page) .include('body') // axe reports there is "no label associated with the text field", when there is one. @@ -21,9 +25,7 @@ async function audit (page) { // axe reports that the back to top button is not inside a landmark, which is intentional. .exclude('.app-back-to-top') - const results = await axe.analyze() - - return results.violations + return axe.analyze() } beforeAll(async () => { @@ -37,41 +39,36 @@ afterAll(async () => { describe('Accessibility Audit', () => { describe('Home page - layout.njk', () => { it('validates', async () => { - await page.goto(baseUrl + '/', { waitUntil: 'load' }) - const violations = await audit(page) - expect(violations).toEqual([]) + const results = await analyze('/') + expect(results).toHaveNoViolations() }) }) describe('Component page - layout-pane.njk', () => { it('validates', async () => { - await page.goto(baseUrl + '/components/radios/', { waitUntil: 'load' }) - const violations = await audit(page) - expect(violations).toEqual([]) + const results = await analyze('/components/radios/') + expect(results).toHaveNoViolations() }) }) describe('Patterns page - layout-pane.njk', () => { it('validates', async () => { - await page.goto(baseUrl + '/patterns/gender-or-sex/', { waitUntil: 'load' }) - const violations = await audit(page) - expect(violations).toEqual([]) + const results = await analyze('/patterns/gender-or-sex/') + expect(results).toHaveNoViolations() }) }) describe('Get in touch page - layout-single-page.njk', () => { it('validates', async () => { - await page.goto(baseUrl + '/get-in-touch/', { waitUntil: 'load' }) - const violations = await audit(page) - expect(violations).toEqual([]) + const results = await analyze('/get-in-touch/') + expect(results).toHaveNoViolations() }) }) describe('Site Map page - layout-sitemap.njk', () => { it('validates', async () => { - await page.goto(baseUrl + '/sitemap/', { waitUntil: 'load' }) - const violations = await audit(page) - expect(violations).toEqual([]) + const results = await analyze('/sitemap/') + expect(results).toHaveNoViolations() }) }) }) diff --git a/jest.setup.js b/jest.setup.js index 0707fc48d7..ff92e6c9e2 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,4 +1,8 @@ /* eslint-env jest */ +const { toHaveNoViolations } = require('jest-axe') + jest.setTimeout(10000) jest.retryTimes(3, { logErrorsBeforeRetry: true }) + +expect.extend(toHaveNoViolations) diff --git a/package-lock.json b/package-lock.json index 5dc494ca3c..d29d49d297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,13 +22,13 @@ "slugger": "^1.0.1" }, "devDependencies": { + "@axe-core/puppeteer": "^4.5.2", "@babel/plugin-transform-modules-commonjs": "^7.20.11", "@metalsmith/in-place": "^4.5.0", "@metalsmith/layouts": "^2.6.0", "@metalsmith/permalinks": "^2.4.1", "@metalsmith/sass": "^1.2.0", "accessible-autocomplete": "^2.0.4", - "axe-puppeteer": "^1.1.1", "browser-sync": "2.27.11", "eslint": "^8.31.0", "eslint-config-standard": "^17.0.0", @@ -37,6 +37,7 @@ "eslint-plugin-promise": "^6.1.1", "iframe-resizer": "^3.6.6", "jest": "^29.3.1", + "jest-axe": "^7.0.0", "jest-environment-jsdom": "^29.3.1", "jest-puppeteer": "^6.2.0", "js-beautify": "^1.14.7", @@ -67,6 +68,30 @@ "npm": "^8.1.0 || ^9.1.0" } }, + "node_modules/@axe-core/puppeteer": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.5.2.tgz", + "integrity": "sha512-KLv+twUPQnKthjpwU96ET7Iw1sAtxRuKQcY1Zko1Q80nQw8X8U6ERDJDD18ogXqXjo7QBz8DmpPNAA6w72pwlg==", + "dev": true, + "dependencies": { + "axe-core": "^4.5.2" + }, + "engines": { + "node": ">=6.4.0" + }, + "peerDependencies": { + "puppeteer": ">=1.10.0 <= 18" + } + }, + "node_modules/@axe-core/puppeteer/node_modules/axe-core": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -3069,31 +3094,6 @@ "postcss": "^8.1.0" } }, - "node_modules/axe-core": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.6.tgz", - "integrity": "sha512-LEUDjgmdJoA3LqklSTwKYqkjcZ4HKc4ddIYGSAiSkr46NTjzg2L9RNB+lekO9P7Dlpa87+hBtzc2Fzn/+GUWMQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/axe-puppeteer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.1.1.tgz", - "integrity": "sha512-bU7dt3zlXrlTmaBsudeACEkQ0O4a5LNxRCdA1Qfph4mqx/DewybPCrlyDqJlHXb/W7ZSodE1fMmC+MgRLizxuQ==", - "deprecated": "deprecated", - "dev": true, - "dependencies": { - "axe-core": "^3.5.3" - }, - "engines": { - "node": ">=6.4.0" - }, - "peerDependencies": { - "puppeteer": ">=1.10.0" - } - }, "node_modules/axios": { "version": "0.21.4", "dev": true, @@ -7461,6 +7461,141 @@ } } }, + "node_modules/jest-axe": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-7.0.0.tgz", + "integrity": "sha512-y+TH+GGH7zOYABWv5IrtGx4ove1dYcVg9Nv9pJZcqKQ+2goJCkAYoHAqergPaV/y9hh1Q9vZykjSXC/2U0Mjyg==", + "dev": true, + "dependencies": { + "axe-core": "4.5.1", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/jest-axe/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/axe-core": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.1.tgz", + "integrity": "sha512-1exVbW0X1O/HSr/WMwnaweyqcWOgZgLiVxdLG34pvSQk4NlYQr9OUy0JLwuhFfuVNQzzqgH57eYzkFBCb3bIsQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-axe/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-axe/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-axe/node_modules/jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-axe/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-changed-files": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", @@ -15709,6 +15844,23 @@ } }, "dependencies": { + "@axe-core/puppeteer": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.5.2.tgz", + "integrity": "sha512-KLv+twUPQnKthjpwU96ET7Iw1sAtxRuKQcY1Zko1Q80nQw8X8U6ERDJDD18ogXqXjo7QBz8DmpPNAA6w72pwlg==", + "dev": true, + "requires": { + "axe-core": "^4.5.2" + }, + "dependencies": { + "axe-core": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", + "dev": true + } + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -17984,21 +18136,6 @@ "postcss-value-parser": "^4.2.0" } }, - "axe-core": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.6.tgz", - "integrity": "sha512-LEUDjgmdJoA3LqklSTwKYqkjcZ4HKc4ddIYGSAiSkr46NTjzg2L9RNB+lekO9P7Dlpa87+hBtzc2Fzn/+GUWMQ==", - "dev": true - }, - "axe-puppeteer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.1.1.tgz", - "integrity": "sha512-bU7dt3zlXrlTmaBsudeACEkQ0O4a5LNxRCdA1Qfph4mqx/DewybPCrlyDqJlHXb/W7ZSodE1fMmC+MgRLizxuQ==", - "dev": true, - "requires": { - "axe-core": "^3.5.3" - } - }, "axios": { "version": "0.21.4", "dev": true, @@ -21161,6 +21298,106 @@ } } }, + "jest-axe": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-7.0.0.tgz", + "integrity": "sha512-y+TH+GGH7zOYABWv5IrtGx4ove1dYcVg9Nv9pJZcqKQ+2goJCkAYoHAqergPaV/y9hh1Q9vZykjSXC/2U0Mjyg==", + "dev": true, + "requires": { + "axe-core": "4.5.1", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "axe-core": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.1.tgz", + "integrity": "sha512-1exVbW0X1O/HSr/WMwnaweyqcWOgZgLiVxdLG34pvSQk4NlYQr9OUy0JLwuhFfuVNQzzqgH57eYzkFBCb3bIsQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + } + }, + "pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jest-changed-files": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", diff --git a/package.json b/package.json index b735efb17c..c56fa98cc7 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,13 @@ "slugger": "^1.0.1" }, "devDependencies": { + "@axe-core/puppeteer": "^4.5.2", "@babel/plugin-transform-modules-commonjs": "^7.20.11", "@metalsmith/in-place": "^4.5.0", "@metalsmith/layouts": "^2.6.0", "@metalsmith/permalinks": "^2.4.1", "@metalsmith/sass": "^1.2.0", "accessible-autocomplete": "^2.0.4", - "axe-puppeteer": "^1.1.1", "browser-sync": "2.27.11", "eslint": "^8.31.0", "eslint-config-standard": "^17.0.0", @@ -51,6 +51,7 @@ "eslint-plugin-promise": "^6.1.1", "iframe-resizer": "^3.6.6", "jest": "^29.3.1", + "jest-axe": "^7.0.0", "jest-environment-jsdom": "^29.3.1", "jest-puppeteer": "^6.2.0", "js-beautify": "^1.14.7", From 83e3ae791f65c37f3999e02959abdec2bef09e66 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:26:06 +0000 Subject: [PATCH 3/9] Pin to `puppeteer@18` using Dependabot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recent axe update can’t use `puppeteer@19` yet ``` npm ERR! Could not resolve dependency: npm ERR! peer puppeteer@">=1.10.0 <= 18" from @axe-core/puppeteer@4.5.2 ``` --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 09c061b66c..9e492fa8c8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,6 +15,10 @@ updates: - dependency-type: direct ignore: + # Ignore major updates (@axe-core/puppeteer peer puppeteer <= 18) + - dependency-name: puppeteer + update-types: ['version-update:semver-major'] + # Ignore major/minor updates (Marked parser changes output) - dependency-name: jstransformer-marked update-types: ['version-update:semver-major', 'version-update:semver-minor'] From 615ee15479388cdd55a4eda4fb7bc2ef3b4b9a0e Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 12 Jan 2023 09:44:03 +0000 Subject: [PATCH 4/9] Turn off axe iframe example reporting This matches axe behaviour from previous releases, but see discussion: https://github.com/alphagov/govuk-design-system/pull/2442#issuecomment-1326600528 --- __tests__/accessiblity-audit.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/__tests__/accessiblity-audit.test.js b/__tests__/accessiblity-audit.test.js index 32d668e923..e769e22788 100644 --- a/__tests__/accessiblity-audit.test.js +++ b/__tests__/accessiblity-audit.test.js @@ -1,4 +1,3 @@ - const { AxePuppeteer } = require('@axe-core/puppeteer') const { setupPage } = require('../lib/jest-utilities.js') @@ -24,6 +23,10 @@ async function analyze (path) { .exclude('.govuk-skip-link') // axe reports that the back to top button is not inside a landmark, which is intentional. .exclude('.app-back-to-top') + // axe reports that the frame "does not have a main landmark" and example

headings + // violate "Heading levels should only increase by one", which is intentional. + // https://github.com/alphagov/govuk-design-system/pull/2442#issuecomment-1326600528 + .exclude('.app-example__frame') return axe.analyze() } From 9c99f53b217bdbb93e80297f0e4bcc72ea445c03 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:08:37 +0000 Subject: [PATCH 5/9] Add Puppeteer helpers from `govuk-frontend` --- lib/puppeteer-helpers.js | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/puppeteer-helpers.js diff --git a/lib/puppeteer-helpers.js b/lib/puppeteer-helpers.js new file mode 100644 index 0000000000..06bbf186db --- /dev/null +++ b/lib/puppeteer-helpers.js @@ -0,0 +1,59 @@ +const configPaths = require('../lib/paths.js') + +const PORT = configPaths.testPort + +/** + * Navigate to path + * + * @param {import('puppeteer').Page} page - Puppeteer page object + * @param {URL['pathname']} path - URL path + * @returns {Promise} Puppeteer page object + */ +async function goTo (page, path) { + const { href } = new URL(path, `http://localhost:${PORT}`) + + await page.goto(href) + await page.bringToFront() + + return page +} + +/** + * Get attribute value for element + * + * @param {import('puppeteer').ElementHandle} $element - Puppeteer element handle + * @param {string} attributeName - Attribute name to return value for + * @returns {Promise} Attribute value + */ +function getAttribute ($element, attributeName) { + return $element.evaluate((el, name) => el.getAttribute(name), attributeName) +} + +/** + * Get property value for element + * + * @param {import('puppeteer').ElementHandle} $element - Puppeteer element handle + * @param {string} propertyName - Property name to return value for + * @returns {Promise} Property value + */ +async function getProperty ($element, propertyName) { + const handle = await $element.getProperty(propertyName) + return handle.jsonValue() +} + +/** + * Check if element is visible + * + * @param {import('puppeteer').ElementHandle} $element - Puppeteer element handle + * @returns {Promise} Element visibility + */ +async function isVisible ($element) { + return !!await $element.boundingBox() +} + +module.exports = { + goTo, + getAttribute, + getProperty, + isVisible +} From fb845c37a417ccc875842edba4e40921ce81e8f8 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:16:15 +0000 Subject: [PATCH 6/9] Update Puppeteer tests using `govuk-frontend` helpers Includes various changes and fixes for the `puppeteer@18` upgrade --- __tests__/accessiblity-audit.test.js | 31 +--- __tests__/back-to-top.test.js | 79 ++++++---- __tests__/component-options.test.js | 37 ++--- __tests__/cookie-banner.test.js | 221 +++++++++++++++------------ __tests__/cookies-page.test.js | 133 ++++++++-------- __tests__/example.test.js | 27 +--- __tests__/navigation.test.js | 88 ++++++----- __tests__/search.test.js | 213 +++++++++++++------------- __tests__/tabs.test.js | 113 +++++++------- lib/jest-utilities.js | 18 --- lib/puppeteer-helpers.js | 5 + 11 files changed, 477 insertions(+), 488 deletions(-) delete mode 100644 lib/jest-utilities.js diff --git a/__tests__/accessiblity-audit.test.js b/__tests__/accessiblity-audit.test.js index e769e22788..73b0421aa4 100644 --- a/__tests__/accessiblity-audit.test.js +++ b/__tests__/accessiblity-audit.test.js @@ -1,16 +1,9 @@ const { AxePuppeteer } = require('@axe-core/puppeteer') -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort +const { goTo } = require('../lib/puppeteer-helpers.js') -let page -const baseUrl = 'http://localhost:' + PORT - -async function analyze (path) { - const { href } = new URL(path, baseUrl) - - await page.goto(href, { waitUntil: 'load' }) +async function analyze (page, path) { + await goTo(page, path) const axe = new AxePuppeteer(page) .include('body') @@ -31,46 +24,38 @@ async function analyze (path) { return axe.analyze() } -beforeAll(async () => { - page = await setupPage() -}) - -afterAll(async () => { - await page.close() -}) - describe('Accessibility Audit', () => { describe('Home page - layout.njk', () => { it('validates', async () => { - const results = await analyze('/') + const results = await analyze(page, '/') expect(results).toHaveNoViolations() }) }) describe('Component page - layout-pane.njk', () => { it('validates', async () => { - const results = await analyze('/components/radios/') + const results = await analyze(page, '/components/radios/') expect(results).toHaveNoViolations() }) }) describe('Patterns page - layout-pane.njk', () => { it('validates', async () => { - const results = await analyze('/patterns/gender-or-sex/') + const results = await analyze(page, '/patterns/gender-or-sex/') expect(results).toHaveNoViolations() }) }) describe('Get in touch page - layout-single-page.njk', () => { it('validates', async () => { - const results = await analyze('/get-in-touch/') + const results = await analyze(page, '/get-in-touch/') expect(results).toHaveNoViolations() }) }) describe('Site Map page - layout-sitemap.njk', () => { it('validates', async () => { - const results = await analyze('/sitemap/') + const results = await analyze(page, '/sitemap/') expect(results).toHaveNoViolations() }) }) diff --git a/__tests__/back-to-top.test.js b/__tests__/back-to-top.test.js index 82bf04e7bc..07c53487d1 100644 --- a/__tests__/back-to-top.test.js +++ b/__tests__/back-to-top.test.js @@ -1,48 +1,63 @@ -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort +const { goTo, isVisible } = require('../lib/puppeteer-helpers.js') -let page -const baseUrl = 'http://localhost:' + PORT +describe('Back to top', () => { + let $module + let $backToTopLink + let pageHeight -beforeAll(async () => { - page = await setupPage() -}) + async function setup (page) { + $module = await page.$('[data-module="app-back-to-top"]') + $backToTopLink = await $module.$('a') -afterAll(async () => { - await page.close() -}) + // Scrollable height of body + pageHeight = await page.$eval('body', ($element) => $element.scrollHeight) ?? 0 + } -const BACK_TO_TOP_LINK_SELECTOR = '[data-module="app-back-to-top"] a' + function scrollTo (page, scrollY) { + return page.evaluate((y) => window.scroll(0, y), scrollY) + } + + beforeEach(async () => { + await page.setJavaScriptEnabled(true) + + await goTo(page, '/styles/colour/') + await scrollTo(page, 0) + await setup(page) + }) -describe('Back to top', () => { it('is always visible when JavaScript is disabled', async () => { await page.setJavaScriptEnabled(false) - await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' }) - const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true }) - expect(isBackToTopVisible).toBeTruthy() + + // Reload page again + await page.reload() + await setup(page) + + // Visible on page + await expect(isVisible($backToTopLink)).resolves.toBe(true) }) + it('is hidden when at the top of the page', async () => { - await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' }) - const isBackToTopHidden = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: false }) - expect(isBackToTopHidden).toBeTruthy() + await scrollTo(page, 0) + + // Visible on page, hidden from viewport + await expect(isVisible($backToTopLink)).resolves.toBe(true) + await expect($backToTopLink.isIntersectingViewport()).resolves.toBe(false) }) + it('is visible when at the bottom of the page', async () => { - await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' }) - // Scroll to the bottom of the page - await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight)) - const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true }) - expect(isBackToTopVisible).toBeTruthy() + await scrollTo(page, pageHeight) + + // Visible on page, shown in viewport + await expect(isVisible($backToTopLink)).resolves.toBe(true) + await expect($backToTopLink.isIntersectingViewport()).resolves.toBe(true) }) + it('goes back to the top of the page when interacted with', async () => { - await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' }) - // Scroll to the bottom of the page - await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight)) - // Make sure the back to top component is available to click - await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true }) - await page.click(BACK_TO_TOP_LINK_SELECTOR) - const isAtTopOfPage = await page.evaluate(() => window.scrollY === 0) - expect(isAtTopOfPage).toBeTruthy() + await scrollTo(page, pageHeight) + await $backToTopLink.click() + + // Scrolled to top + await expect(page.evaluate(() => window.scrollY)).resolves.toBe(0) }) }) diff --git a/__tests__/component-options.test.js b/__tests__/component-options.test.js index 71aba07201..4e2533da19 100644 --- a/__tests__/component-options.test.js +++ b/__tests__/component-options.test.js @@ -1,36 +1,25 @@ -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort - -let page -const baseUrl = 'http://localhost:' + PORT - -beforeEach(async () => { - page = await setupPage() -}) - -afterEach(async () => { - await page.close() -}) +const { goTo } = require('../lib/puppeteer-helpers.js') describe('Component page', () => { it('should contain a "Nunjucks" tab heading', async () => { - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) + await goTo(page, '/components/back-link/') const nunjucksTabHeadings = await page.evaluate(() => Array.from(document.querySelectorAll('.js-tabs__item a')) - .filter(element => element.textContent === 'Nunjucks')) + .filter(element => element.textContent === 'Nunjucks') + ) expect(nunjucksTabHeadings[0]).toBeTruthy() }) it('"Nunjucks" tab content should contain a details summary with "Nunjucks macro options" text', async () => { - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) + await goTo(page, '/components/back-link/') // Get "aria-controls" attributes from "Nunjucks" tab headings const nunjucksTabHeadingControls = await page.evaluateHandle(() => Array.from(document.querySelectorAll('.js-tabs__item a')) .filter(element => element.textContent === 'Nunjucks') - .map(element => element.getAttribute('aria-controls'))) + .map(element => element.getAttribute('aria-controls')) + ) const tabContentIds = await nunjucksTabHeadingControls.jsonValue() // Returns Puppeteer JSONHandle @@ -44,7 +33,7 @@ describe('Component page', () => { }) it('"Nunjucks" tab content should contain a details element that has a table with "Name", "Type" and "Description" column headings', async () => { - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) + await goTo(page, '/components/back-link/') // Get "aria-controls" attributes from "Nunjucks" tab headings const nunjucksTabHeadingControls = await page.evaluateHandle(() => Array.from(document.querySelectorAll('.js-tabs__item a')) @@ -62,24 +51,24 @@ describe('Component page', () => { }) it('macro options should be opened and in view when linked to', async () => { - await page.goto(baseUrl + '/components/back-link/#options-back-link-example', { waitUntil: 'load' }) + await goTo(page, '/components/back-link/#options-back-link-example') // Check if example's macro options details element is open await page.waitForSelector('#options-back-link-example-details[open=open]') // Check if the example has been scrolled into the viewport - const $example = await page.$('#options-back-link-example') - expect(await $example.isIntersectingViewport()).toBe(true) + const $example = await page.$('#options-back-link-example-details') + await expect($example.isIntersectingViewport()).resolves.toBe(true) }) it('macro options subtable should be opened and in view when linked to', async () => { - await page.goto(baseUrl + '/components/text-input/#options-text-input-example--label', { waitUntil: 'load' }) + await goTo(page, '/components/text-input/#options-text-input-example--label') // Check if example's macro options details element is open await page.waitForSelector('#options-text-input-example-details[open=open]') // Check if the example has been scrolled into the viewport const $example = await page.$('#options-text-input-example--label') - expect(await $example.isIntersectingViewport()).toBe(true) + await expect($example.isIntersectingViewport()).resolves.toBe(true) }) }) diff --git a/__tests__/cookie-banner.test.js b/__tests__/cookie-banner.test.js index 1e6728fafe..ff6731c916 100644 --- a/__tests__/cookie-banner.test.js +++ b/__tests__/cookie-banner.test.js @@ -1,175 +1,194 @@ -const { setupPage } = require('../lib/jest-utilities.js') +const { goTo, getAttribute, isVisible } = require('../lib/puppeteer-helpers.js') + const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort -let page -const baseUrl = 'http://localhost:' + PORT +describe('Cookie banner', () => { + let $module + let $message -const COOKIE_BANNER_SELECTOR = '[data-module="govuk-cookie-banner"]' + let $buttonAccept + let $buttonReject -describe('Cookie banner', () => { - beforeEach(async () => { - page = await setupPage() - }) + let $confirmationAccept + let $confirmationReject + + // Default cookie + const cookieParam = { + name: 'design_system_cookies_policy', + value: JSON.stringify({ analytics: true, version: 1 }), + url: `http://localhost:${configPaths.testPort}` + } + + async function setup (page) { + $module = await page.$('[data-module="govuk-cookie-banner"]') + $message = await $module.$('.js-cookie-banner-message') + + // Accept or reject buttons + $buttonAccept = await $module.$('.js-cookie-banner-accept') + $buttonReject = await $module.$('.js-cookie-banner-reject') + + // Accept or reject confirmation messages + $confirmationAccept = await $module.$('.js-cookie-banner-confirmation-accept') + $confirmationReject = await $module.$('.js-cookie-banner-confirmation-reject') + } - afterEach(async () => { - await page.evaluate(() => { - // Delete test cookies - const cookies = document.cookie.split(';') - cookies.forEach(function (cookie) { - const name = cookie.split('=')[0] - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;domain=' + window.location.hostname + ';path=/' - }) + beforeEach(async () => { + await page.deleteCookie({ + name: cookieParam.name, + url: cookieParam.url }) - await page.close() + + await page.setJavaScriptEnabled(true) + + await goTo(page, '/') + await setup(page) }) it('is hidden on the cookies page', async () => { - await page.setCookie({ name: 'design_system_cookies_policy', value: '{"analytics":true, "version":1}', url: baseUrl }) - await page.goto(`${baseUrl}/cookies/`, { waitUntil: 'load' }) + await page.setCookie(cookieParam) + await goTo(page, '/cookies/') - const isCookieBannerHidden = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: false }) - expect(isCookieBannerHidden).toBeTruthy() + await expect(isVisible($module)).resolves.toBe(false) }) describe('when JavaScript is disabled', () => { it('is hidden', async () => { await page.setJavaScriptEnabled(false) - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - const isCookieBannerHidden = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: false }) - expect(isCookieBannerHidden).toBeTruthy() + + // Reload page again + await page.reload() + await setup(page) + + await expect(isVisible($module)).resolves.toBe(false) }) }) describe('when JavaScript is enabled', () => { it('is visible if there is no consent cookie', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - const isCookieBannerVisible = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: true }) - expect(isCookieBannerVisible).toBeTruthy() + await expect(isVisible($module)).resolves.toBe(true) }) it('is visible if the consent cookie version is outdated', async () => { - await page.setCookie({ name: 'design_system_cookies_policy', value: '{"analytics":true, "version":0}', url: baseUrl }) - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - const isCookieBannerVisible = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: true }) - expect(isCookieBannerVisible).toBeTruthy() + const value = JSON.stringify({ analytics: true, version: 0 }) + await page.setCookie({ ...cookieParam, value }) + + // Reload page again + await page.reload() + await setup(page) + + await expect(isVisible($module)).resolves.toBe(true) }) it('is hidden if the consent cookie version is valid', async () => { - await page.setCookie({ name: 'design_system_cookies_policy', value: '{"analytics":true, "version":1}', url: baseUrl }) - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - const isCookieBannerHidden = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: false }) - expect(isCookieBannerHidden).toBeTruthy() + const value = JSON.stringify({ analytics: false, version: 1 }) + await page.setCookie({ ...cookieParam, value }) + + // Reload page again + await page.reload() + await setup(page) + + await expect(isVisible($module)).resolves.toBe(false) }) }) describe('accept button', () => { it('sets the consent cookie', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - - const initialCookie = await page.cookies() - expect(initialCookie).toEqual([]) - - await page.click('.js-cookie-banner-accept') - const newCookie = await page.cookies() - expect(newCookie[0].name).toEqual('design_system_cookies_policy') - expect(newCookie[0].value).toEqual('{"analytics":true,"version":1}') + await expect(page.cookies()).resolves.toEqual([]) + await $buttonAccept.click() + + await expect(page.cookies()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: cookieParam.name, + value: JSON.stringify({ analytics: true, version: 1 }) + }) + ]) + ) }) it('hides the cookie message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-accept') - - const isCookieMessageHidden = await page.waitForSelector('.js-cookie-banner-message', { visible: false }) - expect(isCookieMessageHidden).toBeTruthy() + await $buttonAccept.click() + await expect(isVisible($module)).resolves.toBe(true) }) it('shows the confirmation message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-accept') - - const isConfirmationMessageVisible = await page.waitForSelector('.js-cookie-banner-confirmation-accept', { visible: true }) - expect(isConfirmationMessageVisible).toBeTruthy() + await $buttonAccept.click() + await expect(isVisible($confirmationAccept)).resolves.toBe(true) }) it('moves user focus to the confirmation message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-accept') - - const confirmationMessageTabindex = await page.evaluate(() => document.body.querySelector('.js-cookie-banner-confirmation-accept').getAttribute('tabindex')) - expect(confirmationMessageTabindex).toEqual('-1') + await $buttonAccept.click() + await expect(getAttribute($confirmationAccept, 'tabindex')).resolves.toEqual('-1') }) }) describe('reject button', () => { it('sets the consent cookie', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - - const initialCookie = await page.cookies() - expect(initialCookie).toEqual([]) - - await page.click('.js-cookie-banner-reject') - const newCookie = await page.cookies() - expect(newCookie[0].name).toEqual('design_system_cookies_policy') - expect(newCookie[0].value).toEqual('{"analytics":false,"version":1}') + await expect(page.cookies()).resolves.toEqual([]) + await $buttonReject.click() + + await expect(page.cookies()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: cookieParam.name, + value: JSON.stringify({ analytics: false, version: 1 }) + }) + ]) + ) }) it('hides the cookie message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-reject') - - const isCookieMessageHidden = await page.waitForSelector('.js-cookie-banner-message', { visible: false }) - expect(isCookieMessageHidden).toBeTruthy() + await $buttonReject.click() + await expect(isVisible($message)).resolves.toBe(false) }) it('shows the confirmation message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-reject') - - const isConfirmationMessageVisible = await page.waitForSelector('.js-cookie-banner-confirmation-reject', { visible: true }) - expect(isConfirmationMessageVisible).toBeTruthy() + await $buttonReject.click() + await expect(isVisible($confirmationReject)).resolves.toBe(true) }) it('moves user focus to the confirmation message', async () => { - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-reject') - - const confirmationMessageTabindex = await page.evaluate(() => document.body.querySelector('.js-cookie-banner-confirmation-reject').getAttribute('tabindex')) - expect(confirmationMessageTabindex).toEqual('-1') + await $buttonReject.click() + await expect(getAttribute($confirmationReject, 'tabindex')).resolves.toEqual('-1') }) }) describe('hide button', () => { it('hides the accept confirmation message', async () => { + const $buttonAcceptHide = await $module.$('.js-cookie-banner-hide--accept') + // Accept cookies - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-accept') + await $buttonAccept.click() - // Click the hide button - await page.click('.js-cookie-banner-hide--accept') + await expect(isVisible($message)).resolves.toBe(false) + await expect(isVisible($confirmationAccept)).resolves.toBe(true) + await expect(isVisible($module)).resolves.toBe(true) - const isConfirmationMessageHidden = await page.waitForSelector('.js-cookie-banner-confirmation-accept', { visible: false }) - const isCookieBannerHidden = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: false }) + // Click the hide button + await $buttonAcceptHide.click() - expect(isConfirmationMessageHidden).toBeTruthy() - expect(isCookieBannerHidden).toBeTruthy() + await expect(isVisible($message)).resolves.toBe(false) + await expect(isVisible($confirmationAccept)).resolves.toBe(false) + await expect(isVisible($module)).resolves.toBe(false) }) it('hides the reject confirmation message', async () => { + const $buttonRejectHide = await $module.$('.js-cookie-banner-hide--reject') + // Reject cookies - await page.goto(`${baseUrl}`, { waitUntil: 'load' }) - await page.click('.js-cookie-banner-reject') + await $buttonReject.click() - // Click the hide button - await page.click('.js-cookie-banner-hide--reject') + await expect(isVisible($message)).resolves.toBe(false) + await expect(isVisible($confirmationReject)).resolves.toBe(true) + await expect(isVisible($module)).resolves.toBe(true) - const isConfirmationMessageHidden = await page.waitForSelector('.js-cookie-banner-confirmation-reject', { visible: false }) - const isCookieBannerHidden = await page.waitForSelector(COOKIE_BANNER_SELECTOR, { visible: false }) + // Click the hide button + await $buttonRejectHide.click() - expect(isConfirmationMessageHidden).toBeTruthy() - expect(isCookieBannerHidden).toBeTruthy() + await expect(isVisible($message)).resolves.toBe(false) + await expect(isVisible($confirmationReject)).resolves.toBe(false) + await expect(isVisible($module)).resolves.toBe(false) }) }) }) diff --git a/__tests__/cookies-page.test.js b/__tests__/cookies-page.test.js index feb8099afe..3403fc09ff 100644 --- a/__tests__/cookies-page.test.js +++ b/__tests__/cookies-page.test.js @@ -1,89 +1,81 @@ +const { goTo, getProperty, isVisible } = require('../lib/puppeteer-helpers.js') -const { setupPage } = require('../lib/jest-utilities.js') const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort -let page -const baseUrl = 'http://localhost:' + PORT +describe('Cookies page', () => { + let $module -const cookiesPageSelector = '[data-module="app-cookies-page"]' + let $radioYes + let $radioNo + let $buttonSave + + async function setup (page) { + $module = await page.$('[data-module="app-cookies-page"]') + $radioYes = await $module.$('input[name="analytics"][value="yes"]') + $radioNo = await $module.$('input[name="analytics"][value="no"]') + $buttonSave = await $module.$('button') + } -describe('Cookies page', () => { beforeEach(async () => { - page = await setupPage() + await page.deleteCookie({ + name: 'design_system_cookies_policy', + url: `http://localhost:${configPaths.testPort}` + }) + await page.setJavaScriptEnabled(true) - await page.goto(`${baseUrl}/cookies`) - }) - afterEach(async () => { - await page.evaluate(() => { - // Delete test cookies - const cookies = document.cookie.split(';') - cookies.forEach(function (cookie) { - const name = cookie.split('=')[0] - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;domain=' + window.location.hostname + ';path=/' - }) - }) - await page.close() + await goTo(page, '/cookies') + await setup(page) }) it('without JavaScript it has no visible inputs', async () => { await page.setJavaScriptEnabled(false) - await page.goto(`${baseUrl}/cookies`) - const isAnalyticsFormGroupHidden = await page.waitForSelector( - cookiesPageSelector + ' .govuk-form-group input[type="radio"][name="analytics"]', { hidden: true } - ) - expect(isAnalyticsFormGroupHidden).toBeTruthy() + // Reload page again + await page.reload() + await setup(page) - const isSaveButtonHidden = await page.waitForSelector( - cookiesPageSelector + ' button', { hidden: true } - ) - expect(isSaveButtonHidden).toBeTruthy() + await expect(isVisible($radioYes)).resolves.toBe(false) + await expect(isVisible($radioNo)).resolves.toBe(false) + await expect(isVisible($buttonSave)).resolves.toBe(false) }) it('has radios for each cookie type', async () => { - const isAnalyticsFormGroupVisible = await page.waitForSelector( - cookiesPageSelector + ' .govuk-form-group input[type="radio"][name="analytics"]', { visible: true } - ) - expect(isAnalyticsFormGroupVisible).toBeTruthy() + await expect(isVisible($radioYes)).resolves.toBe(true) + await expect(isVisible($radioNo)).resolves.toBe(true) }) it('sets the default radio selection to "no"', async () => { - expect(await page.cookies()).toEqual([]) + await expect(page.cookies()).resolves.toEqual([]) - const isAnalyticsDisagreeSelected = await page.waitForSelector(cookiesPageSelector + ' input[name="analytics"][value="no"]:checked', { visible: true }) - expect(isAnalyticsDisagreeSelected).toBeTruthy() + await expect(getProperty($radioYes, 'checked')).resolves.toBe(false) + await expect(getProperty($radioNo, 'checked')).resolves.toBe(true) }) it('has a save button', async () => { - const isSaveButtonVisible = await page.waitForSelector( - cookiesPageSelector + ' button', { visible: true } - ) - expect(isSaveButtonVisible).toBeTruthy() + await expect(isVisible($buttonSave)).resolves.toBe(true) }) it('shows success notification banner after preferences are saved', async () => { - const isSuccessNotificationHidden = await page.waitForSelector( - cookiesPageSelector + ' .govuk-notification-banner--success', { hidden: true } - ) - expect(isSuccessNotificationHidden).toBeTruthy() + const $notification = await $module.$('.govuk-notification-banner--success') - await page.click(cookiesPageSelector + ' input[name="analytics"]') - await page.click(cookiesPageSelector + ' button') + // Notification hidden + await expect(isVisible($notification)).resolves.toBe(false) - const isSuccessNotificationVisible = await page.waitForSelector( - cookiesPageSelector + ' .govuk-notification-banner--success', { visible: true } - ) - expect(isSuccessNotificationVisible).toBeTruthy() + // Click 'Yes' and submit + await $radioYes.click() + await $buttonSave.click() + + // Notification visible + await expect(isVisible($notification)).resolves.toBe(true) }) it('saves user preferences to a cookie', async () => { - await page.click(cookiesPageSelector + ' input[name="analytics"][value="yes"]') - await page.click(cookiesPageSelector + ' button') + // Click 'Yes' and submit + await $radioYes.click() + await $buttonSave.click() - expect(await page.cookies()).toEqual( + await expect(page.cookies()).resolves.toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'design_system_cookies_policy', @@ -92,10 +84,11 @@ describe('Cookies page', () => { ]) ) - await page.click(cookiesPageSelector + ' input[name="analytics"][value="no"]') - await page.click(cookiesPageSelector + ' button') + // Click 'No' and submit + await $radioNo.click() + await $buttonSave.click() - expect(await page.cookies()).toEqual( + await expect(page.cookies()).resolves.toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'design_system_cookies_policy', @@ -106,22 +99,26 @@ describe('Cookies page', () => { }) it('shows the users existing preferences when the page is loaded', async () => { - await page.click(cookiesPageSelector + ' input[name="analytics"][value="no"]') - await page.click(cookiesPageSelector + ' button') + // Click 'No' and submit + await $radioNo.click() + await $buttonSave.click() - await page.goto(`${baseUrl}/cookies`) + // Reload page again + await page.reload() + await setup(page) - const isAnalyticsDisagreeSelected = await page.waitForSelector(cookiesPageSelector + ' input[name="analytics"][value="no"]:checked', { visible: true }) - expect(isAnalyticsDisagreeSelected).toBeTruthy() + await expect(getProperty($radioYes, 'checked')).resolves.toBe(false) + await expect(getProperty($radioNo, 'checked')).resolves.toBe(true) - await page.click(cookiesPageSelector + ' input[name="analytics"][value="yes"]') - await page.click(cookiesPageSelector + ' button') + // Click 'Yes', submit form + await $radioYes.click() + await $buttonSave.click() - await page.goto(`${baseUrl}/cookies`) + // Reload page again + await page.reload() + await setup(page) - const isAnalyticsAgreeSelected = await page.waitForSelector( - cookiesPageSelector + ' input[name="analytics"][value="yes"]:checked', { visible: true } - ) - expect(isAnalyticsAgreeSelected).toBeTruthy() + await expect(getProperty($radioYes, 'checked')).resolves.toBe(true) + await expect(getProperty($radioNo, 'checked')).resolves.toBe(false) }) }) diff --git a/__tests__/example.test.js b/__tests__/example.test.js index 1cca1af15e..c106d3ac72 100644 --- a/__tests__/example.test.js +++ b/__tests__/example.test.js @@ -1,29 +1,18 @@ -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort - -let page -const baseUrl = 'http://localhost:' + PORT - -beforeAll(async () => { - page = await setupPage() -}) - -afterAll(async () => { - await page.close() -}) +const { goTo } = require('../lib/puppeteer-helpers.js') describe('Example page', () => { describe('that has a form', () => { it('does not submit the form / reload the page', async () => { - const defaultExampleUrl = baseUrl + '/patterns/question-pages/default/' - await page.goto(defaultExampleUrl, { waitUntil: 'load' }) + const pathname = '/patterns/question-pages/default/' + + await goTo(page, pathname) await page.waitForSelector('form[action="/form-handler"]') await page.click('.govuk-button') - const url = await page.url() - // url should stay the same as the form shouldn't submit - expect(url).toBe(defaultExampleUrl) + + // Still on same page (form not submitted) + const url = new URL(await page.url()) + expect(url.pathname).toBe(pathname) }) }) }) diff --git a/__tests__/navigation.test.js b/__tests__/navigation.test.js index eb42c9ccce..5956859932 100644 --- a/__tests__/navigation.test.js +++ b/__tests__/navigation.test.js @@ -1,80 +1,78 @@ -const devices = require('puppeteer/DeviceDescriptors') -const iPhone = devices['iPhone 6'] +const { devices } = require('puppeteer') -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort +const { goTo, getAttribute, isVisible } = require('../lib/puppeteer-helpers.js') -let page -const baseUrl = 'http://localhost:' + PORT +describe('Homepage', () => { + let $navigation + let $navigationToggler -const mobileNav = '.app-navigation' -const mobileNavToggler = '.js-app-navigation__toggler' + async function setup (page) { + $navigation = await page.$('.js-app-navigation') + $navigationToggler = await page.$('.js-app-navigation__toggler') + } -beforeAll(async () => { - page = await setupPage(iPhone) -}) + beforeAll(async () => { + await page.emulate(devices['iPhone 6']) + }) -afterAll(async () => { - await page.close() -}) + beforeEach(async () => { + await page.setJavaScriptEnabled(true) + + await goTo(page, '/') + await setup(page) + }) -describe('Homepage', () => { describe('when JavaScript is unavailable or fails', () => { it('falls back to making the navigation visible', async () => { await page.setJavaScriptEnabled(false) - await page.goto(baseUrl, { waitUntil: 'load' }) - const isAppNavigationVisible = await page.waitForSelector('.js-app-navigation', { visible: true, timeout: 1000 }) - expect(isAppNavigationVisible).toBeTruthy() + + // Reload page again + await page.reload() + await setup(page) + + // Menu open state (visually) + await expect(isVisible($navigation)).resolves.toBe(true) }) }) describe('when JavaScript is available', () => { describe('when menu button is pressed', () => { it('should apply the corresponding open state class to the menu button', async () => { - await page.setJavaScriptEnabled(true) - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.click(mobileNavToggler) + await expect(getAttribute($navigationToggler, 'class')).resolves + .not.toContain('govuk-header__menu-button--open') - const toggleButtonIsOpen = await page.evaluate((mobileNavToggler) => - document.body.querySelector(mobileNavToggler).classList.contains('govuk-header__menu-button--open'), - mobileNavToggler) + await $navigationToggler.click() - expect(toggleButtonIsOpen).toBeTruthy() + // Menu button open state + await expect(getAttribute($navigationToggler, 'class')).resolves + .toContain('govuk-header__menu-button--open') }) it('should indicate the expanded state of the toggle button using aria-expanded', async () => { - await page.setJavaScriptEnabled(true) - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.click(mobileNavToggler) + await expect(getAttribute($navigationToggler, 'aria-expanded')).resolves.toBe('false') - const toggleButtonAriaExpanded = await page.evaluate((mobileNavToggler) => - document.body.querySelector(mobileNavToggler).getAttribute('aria-expanded'), - mobileNavToggler) + await $navigationToggler.click() - expect(toggleButtonAriaExpanded).toBe('true') + // Menu button control expanded + await expect(getAttribute($navigationToggler, 'aria-expanded')).resolves.toBe('true') }) it('should indicate the open state of the navigation', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.click(mobileNavToggler) + await expect(getAttribute($navigation, 'class')).resolves.not.toContain('app-navigation--active') - const navigationIsOpen = await page.evaluate((mobileNav) => - document.body.querySelector(mobileNav).classList.contains('app-navigation--active'), - mobileNav) + await $navigationToggler.click() - expect(navigationIsOpen).toBeTruthy() + // Menu open state + await expect(getAttribute($navigation, 'class')).resolves.toContain('app-navigation--active') }) it('should indicate the visible state of the navigation using the hidden attribute', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.click(mobileNavToggler) + await expect(isVisible($navigation)).resolves.toBe(false) - const navigationIsHidden = await page.evaluate((mobileNav) => - document.body.querySelector(mobileNav).hasAttribute('hidden'), - mobileNav) + await $navigationToggler.click() - expect(navigationIsHidden).toBe(false) + // Menu open state (visually) + await expect(isVisible($navigation)).resolves.toBe(true) }) }) }) diff --git a/__tests__/search.test.js b/__tests__/search.test.js index 2d34c92f46..8126002c1f 100644 --- a/__tests__/search.test.js +++ b/__tests__/search.test.js @@ -1,151 +1,158 @@ -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort +const { goTo, getProperty } = require('../lib/puppeteer-helpers.js') // Regex that can be used to match on fingerprinted search index files const isSearchIndex = /.*\/search-index-[0-9a-f]{32}.json$/ -let page -const baseUrl = 'http://localhost:' + PORT +describe('Site search', () => { + let $module -beforeEach(async () => { - page = await setupPage() -}) + let $wrapper + let $searchInput -afterEach(async () => { - await page.close() -}) + async function setup (page) { + $module = await page.$('[data-module="app-search"]') -describe('Site search', () => { - it('does not return any results when searching for something that does not exist', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + $wrapper = await $module.$('.app-site-search__wrapper') + $searchInput = await $module.$('.app-site-search__input') + } + + beforeEach(async () => { + await page.setRequestInterception(false) - await page.waitForSelector('.app-site-search__input') - await page.type('.app-site-search__input', 'lorem ipsum') - const optionResult = await page.$eval('.app-site-search__option', option => option.textContent) + // Remove page listeners + page.removeAllListeners('dialog') + page.removeAllListeners('request') - expect(optionResult).toBe('No results found') + await goTo(page, '/') + await setup(page) }) + + afterEach(async () => { + // Reset 'onbeforeunload' to continue navigation + await page.evaluate(() => { window.onbeforeunload = null }) + }) + + it('does not return any results when searching for something that does not exist', async () => { + await $searchInput.type('lorem ipsum') + + const $searchOptions = await $module.$$('.app-site-search__option') + await expect(getProperty($searchOptions[0], 'textContent')).resolves.toBe('No results found') + }) + it('returns results where a word in the title begins with the letter "d"', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + await $searchInput.type('d') - await page.waitForSelector('.app-site-search__input') - await page.type('.app-site-search__input', 'd') const resultsArray = await page.evaluate( () => [...document.querySelectorAll('.app-site-search__option')] - // ignore any results where a match was found in the alias + // ignore any results where a match was found in the alias .filter(elem => !elem.querySelector('.app-site-search__aliases')) - // only get text, ignore child nodes + // only get text, ignore child nodes .map(elem => elem.firstChild.textContent.toLowerCase()) ) + // regex with word boundary, in our case words that begin with 'd' expect(resultsArray.every(item => (/\b[d]\w*/).test(item))).toBeTruthy() }) - it('returns results that contain aliases that start with the letter "d"', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.waitForSelector('.app-site-search__input') - await page.type('.app-site-search__input', 'd') + it('returns results that contain aliases that start with the letter "d"', async () => { + await $searchInput.type('d') const resultsArray = await page.evaluate( () => [...document.querySelectorAll('.app-site-search__option')] - // only get results where a match was found in the alias + // only get results where a match was found in the alias .filter(elem => elem.querySelector('.app-site-search__aliases')) .map(elem => elem.querySelector('.app-site-search__aliases').textContent) ) + // regex with word boundary, in our case words that begin with 'd' expect(resultsArray.every(item => (/\b[d]\w*/).test(item))).toBeTruthy() }) + it('doesn\'t show any aliases if it finds any matches in the title', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + await $searchInput.type('det') - await page.waitForSelector('.app-site-search__input') - await page.type('.app-site-search__input', 'det') const resultsArray = await page.evaluate( () => [...document.querySelectorAll('.app-site-search__aliases')] .map(elem => elem.querySelector('.app-site-search__aliases')) ) + expect(resultsArray).toHaveLength(0) }) + it('selecting "details" as the result takes you to the the "details" page', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + await $searchInput.click() + await $searchInput.type('details') - await page.waitForSelector('.app-site-search__input') - await page.click('.app-site-search__input') - await page.type('.app-site-search__input', 'details') await Promise.all([ page.waitForNavigation(), page.keyboard.press('Enter') ]) - const url = await page.url() - expect(url).toBe(baseUrl + '/components/details/') + const url = new URL(await page.url()) + expect(url.pathname).toBe('/components/details/') }) + it('shows user a message that the index has failed to download', async () => { await page.setRequestInterception(true) - page.on('request', interceptedRequest => { - if (isSearchIndex.test(interceptedRequest.url())) { - interceptedRequest.abort() - } else { - interceptedRequest.continue() - } - }) - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.waitForSelector('.app-site-search__input') - await page.click('.app-site-search__input') - await page.type('.app-site-search__input', 'lorem') - const optionResult = await page.$eval('.app-site-search__option', option => option.textContent) - expect(optionResult).toBe('Failed to load the search index') + // Abort requests to search index + page.on('request', request => + isSearchIndex.test(request.url()) + ? request.abort() + : request.continue() + ) + + // Reload page again + await page.reload() + await setup(page) + + await $searchInput.click() + await $searchInput.type('lorem') + + const $searchOptions = await $module.$$('.app-site-search__option') + await expect(getProperty($searchOptions[0], 'textContent')).resolves.toBe('Failed to load the search index') }) it('shows user a message that the search index is loading', async () => { await page.setRequestInterception(true) - page.on('request', interceptedRequest => { - // Intentionally make the search-index request hang - if (!isSearchIndex.test(interceptedRequest.url())) { - interceptedRequest.continue() - } - }) - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.waitForSelector('.app-site-search__input') - await page.click('.app-site-search__input') - await page.type('.app-site-search__input', 'd') - const optionResult = await page.$eval('.app-site-search__option', option => option.textContent) - expect(optionResult).toBe('Loading search index') - }) + // Intentionally make the search-index request hang + page.on('request', request => isSearchIndex.test(request.url()) || request.continue()) - it('should focus the input when clicking the button', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + // Reload page again + await page.reload() + await setup(page) + + await $searchInput.click() + await $searchInput.type('d') - await page.waitForSelector('.app-site-search__input') + const $searchOptions = await $module.$$('.app-site-search__option') + await expect(getProperty($searchOptions[0], 'textContent')).resolves.toBe('Loading search index') + }) + it('should focus the input when clicking the button', async () => { // The button is actually a background on the left most part of the wrapper element. - const wrapperCoordinates = await page.$eval('.app-site-search__wrapper', $wrapper => { - const { top, left } = $wrapper.getBoundingClientRect() - return { top, left } - }) + const { top, left } = await $wrapper.evaluate(($element) => + $element.getBoundingClientRect().toJSON()) + // Click the top left side of the element. - await page.mouse.click(wrapperCoordinates.left, wrapperCoordinates.top) + await page.mouse.click(left, top) // Get the active focused element to compare with the actual expected input. const $activeElement = await page.evaluate(() => document.activeElement) - const $input = await page.evaluate(() => document.querySelector('.app-site-search__input')) + const $searchInput = await page.evaluate(() => document.querySelector('.app-site-search__input')) - expect($activeElement).toEqual($input) + expect($activeElement).toEqual($searchInput) }) describe('tracking', () => { it('should track if there are no results', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) - await page.evaluate(() => { window.__SITE_SEARCH_TRACKING_TIMEOUT = 0 }) - await page.waitForSelector('.app-site-search__input') - await page.focus('.app-site-search__input') - await page.type('.app-site-search__input', 'lorem ipsum') + await $searchInput.focus() + await $searchInput.type('lorem ipsum') + const GoogleTagManagerDataLayer = await page.evaluate(() => window.dataLayer) expect(GoogleTagManagerDataLayer).toEqual( @@ -164,15 +171,14 @@ describe('Site search', () => { ]) ) }) - it('should track if there are results', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + it('should track if there are results', async () => { await page.evaluate(() => { window.__SITE_SEARCH_TRACKING_TIMEOUT = 0 }) - await page.waitForSelector('.app-site-search__input') - await page.focus('.app-site-search__input') - await page.type('.app-site-search__input', 'g') - const optionResults = await page.$$('.app-site-search__option') + await $searchInput.focus() + await $searchInput.type('g') + + const $searchOptions = await $module.$$('.app-site-search__option') const GoogleTagManagerDataLayer = await page.evaluate(() => window.dataLayer) // Find layer that has the impressions to test. @@ -181,7 +187,7 @@ describe('Site search', () => { .filter(layer => layer.ecommerce) .map(layer => layer.ecommerce.impressions)[0] - expect(impressions.length).toEqual(optionResults.length) + expect(impressions.length).toEqual($searchOptions.length) expect(GoogleTagManagerDataLayer).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -205,22 +211,17 @@ describe('Site search', () => { ]) ) }) - it('should track if a result is clicked', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + it('should track if a result is clicked', async () => { // Prevent page from unloading so we can check what was tracked. // By setting onbeforeunload it forces a dialog to appear that allows a user // to cancel leaving the page, so we detect the dialog opening and dismiss it to stop the navigation. - await page.evaluate(() => { - window.onbeforeunload = () => true - }) - page.on('dialog', async dialog => { - await dialog.dismiss() - }) - - await page.waitForSelector('.app-site-search__input') - await page.focus('.app-site-search__input') - await page.type('.app-site-search__input', 'g') + await page.evaluate(() => { window.onbeforeunload = () => true }) + page.on('dialog', async dialog => { await dialog.dismiss() }) + + await $searchInput.focus() + await $searchInput.type('g') + await page.keyboard.press('ArrowDown') await page.keyboard.press('Enter') @@ -254,14 +255,13 @@ describe('Site search', () => { ]) ) }) - it('should block personally identifable information emails', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + it('should block personally identifable information emails', async () => { await page.evaluate(() => { window.__SITE_SEARCH_TRACKING_TIMEOUT = 0 }) - await page.waitForSelector('.app-site-search__input') - await page.focus('.app-site-search__input') - await page.type('.app-site-search__input', 'user@example.com') + await $searchInput.focus() + await $searchInput.type('user@example.com') + const GoogleTagManagerDataLayer = await page.evaluate(() => window.dataLayer) expect(GoogleTagManagerDataLayer).toEqual( @@ -274,14 +274,13 @@ describe('Site search', () => { ]) ) }) - it('should block personally identifable information numbers', async () => { - await page.goto(baseUrl, { waitUntil: 'load' }) + it('should block personally identifable information numbers', async () => { await page.evaluate(() => { window.__SITE_SEARCH_TRACKING_TIMEOUT = 0 }) - await page.waitForSelector('.app-site-search__input') - await page.focus('.app-site-search__input') - await page.type('.app-site-search__input', '079460999') + await $searchInput.focus() + await $searchInput.type('079460999') + const GoogleTagManagerDataLayer = await page.evaluate(() => window.dataLayer) expect(GoogleTagManagerDataLayer).toEqual( diff --git a/__tests__/tabs.test.js b/__tests__/tabs.test.js index 35f7e3b8c6..7e7169249b 100644 --- a/__tests__/tabs.test.js +++ b/__tests__/tabs.test.js @@ -1,106 +1,117 @@ -const { setupPage } = require('../lib/jest-utilities.js') -const configPaths = require('../lib/paths.js') -const PORT = configPaths.testPort +const { goTo, getAttribute, isVisible } = require('../lib/puppeteer-helpers.js') -let page -const baseUrl = 'http://localhost:' + PORT +describe('Component page', () => { + let $module -beforeAll(async () => { - page = await setupPage() -}) + let $tabsItems + let $tabsLinks + let $tabsContainers -afterAll(async () => { - await page.close() -}) + async function setup (page) { + $module = await page.$('[data-module="app-tabs"]') + + $tabsItems = await $module.$$('.app-tabs__item') + $tabsLinks = await $module.$$('.app-tabs__item a') + $tabsContainers = await $module.$$('.js-tabs__container') + } + + beforeEach(async () => { + await page.setJavaScriptEnabled(true) + + await goTo(page, '/components/back-link/') + await setup(page) + }) -describe('Component page', () => { describe('when JavaScript is unavailable or fails', () => { it('falls back to making the containers visible', async () => { await page.setJavaScriptEnabled(false) - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) - const isContainerVisible = await page.waitForSelector('.js-tabs__container', { visible: true, timeout: 1000 }) - expect(isContainerVisible).toBeTruthy() + + // Reload page again + await page.reload() + await setup(page) + + for await (const isTabVisible of $tabsContainers.map(isVisible)) { + expect(isTabVisible).toBe(true) + } }) }) describe('when JavaScript is available', () => { describe('when tab item is pressed', () => { it('should indicate the open state of the tab', async () => { - await page.setJavaScriptEnabled(true) - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) - - await page.click('.js-tabs__item a') + await $tabsLinks[0].click() - const toggleButtonIsOpen = await page.evaluate(() => document.body.querySelector('.app-tabs__item').classList.contains('app-tabs__item--current')) - expect(toggleButtonIsOpen).toBeTruthy() + // Tab item marked current + await expect(getAttribute($tabsItems[0], 'class')).resolves + .toContain('app-tabs__item--current') }) it('should indicate the selected state of the tab using aria-expanded', async () => { - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) - - await page.click('.js-tabs__item a') + await $tabsLinks[0].click() - const toggleButtonAriaExpanded = await page.evaluate(() => document.body.querySelector('.js-tabs__item a').getAttribute('aria-expanded')) - expect(toggleButtonAriaExpanded).toBe('true') + // Tab link control expanded + await expect(getAttribute($tabsLinks[0], 'aria-expanded')).resolves + .toBe('true') }) }) describe('when the tab closed and clicked twice', () => { it('should indicate the closed state of the tab', async () => { - await page.setJavaScriptEnabled(true) - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) + await $tabsLinks[0].click() + await $tabsLinks[0].click() - await page.click('.js-tabs__item a') - await page.click('.js-tabs__item a') - - const toggleButtonIsOpen = await page.evaluate(() => document.body.querySelector('.app-tabs__item').classList.contains('app-tabs__item--current')) - expect(toggleButtonIsOpen).toBeFalsy() + // Tab item not marked current + await expect(getAttribute($tabsItems[0], 'class')).resolves + .not.toContain('app-tabs__item--current') }) it('should indicate the closed state by setting aria-expanded attribute to false', async () => { - await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' }) - - await page.click('.js-tabs__item a') - await page.click('.js-tabs__item a') + await $tabsLinks[0].click() + await $tabsLinks[0].click() - const toggleButtonAriaExpanded = await page.evaluate(() => document.body.querySelector('.js-tabs__item a').getAttribute('aria-expanded')) - expect(toggleButtonAriaExpanded).toBe('false') + // Tab link control expanded + await expect(getAttribute($tabsLinks[0], 'aria-expanded')).resolves + .toBe('false') }) }) }) }) describe('Patterns page', () => { + beforeEach(async () => { + await goTo(page, '/patterns/question-pages/') + }) + describe('when JavaScript is available', () => { describe('when "hideTab" parameter is set to true', () => { it('the tab list is not rendered', async () => { - await page.goto(baseUrl + '/patterns/question-pages/', { waitUntil: 'load' }) - const expandedTabContentWithNoTab = await page.evaluate(() => document.body.querySelector('#section-headings-question-pages-example-open .app-tabs')) - expect(expandedTabContentWithNoTab).toBeNull() + const $expandedTabContentWithNoTab = await page.$('#section-headings-question-pages-example-open .app-tabs') + expect($expandedTabContentWithNoTab).toBeNull() }) it('close button is not shown on the code block', async () => { - await page.goto(baseUrl + '/patterns/question-pages/', { waitUntil: 'load' }) - const expandedTabContentWithNoTabCloseButton = await page.evaluate(() => document.body.querySelector('.js-tabs__container--no-tabs .js-tabs__close')) - expect(expandedTabContentWithNoTabCloseButton).toBeNull() + const $expandedTabContentWithNoTabCloseButton = await page.$('.js-tabs__container--no-tabs .js-tabs__close') + expect($expandedTabContentWithNoTabCloseButton).toBeNull() }) }) }) }) describe('Styles -> Images page', () => { + beforeEach(async () => { + await goTo(page, '/styles/images/') + }) + describe('when both nunjucks and html parameters are set to "false"', () => { it('the tab list items are not rendered', async () => { - await page.goto(baseUrl + '/styles/images/', { waitUntil: 'load' }) - const tabListItems = await page.evaluate(() => document.body.querySelector('#example-default .app-tabs')) - expect(tabListItems).toBeFalsy() + const $tabListItems = await page.$('#example-default .app-tabs') + expect($tabListItems).toBeNull() }) it('the tab heading items are not rendered', async () => { - await page.goto(baseUrl + '/styles/images/', { waitUntil: 'load' }) - const tabHeadingItems = await page.evaluate(() => document.body.querySelector('#example-default .app-tabs__heading')) - expect(tabHeadingItems).toBeFalsy() + const $tabHeadingItems = await page.$('#example-default .app-tabs__heading') + expect($tabHeadingItems).toBeNull() }) }) }) diff --git a/lib/jest-utilities.js b/lib/jest-utilities.js deleted file mode 100644 index edcf18a714..0000000000 --- a/lib/jest-utilities.js +++ /dev/null @@ -1,18 +0,0 @@ -async function setupPage (emulate) { - const page = await global.browser.newPage() - - if (emulate) { - await page.emulate(emulate) - } - - // Capture JavaScript errors. - page.on('pageerror', error => { - throw error - }) - - return page -} - -module.exports = { - setupPage -} diff --git a/lib/puppeteer-helpers.js b/lib/puppeteer-helpers.js index 06bbf186db..d7feed91f0 100644 --- a/lib/puppeteer-helpers.js +++ b/lib/puppeteer-helpers.js @@ -12,6 +12,11 @@ const PORT = configPaths.testPort async function goTo (page, path) { const { href } = new URL(path, `http://localhost:${PORT}`) + // Navigate to blank page first to fix same page + // hash fragment changes (window.location.hash) + await page.goto('about:blank') + + // Navigate to page await page.goto(href) await page.bringToFront() From b5c6016c3ef9aa8b21b659646bbc9799725a887b Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:09:19 +0000 Subject: [PATCH 7/9] =?UTF-8?q?Run=20Puppeteer=20(Chromium)=20in=20?= =?UTF-8?q?=E2=80=98incognito=E2=80=99=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest-puppeteer.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js index 4a91a02b94..ae5597de41 100644 --- a/jest-puppeteer.config.js +++ b/jest-puppeteer.config.js @@ -1,7 +1,9 @@ const configPaths = require('./lib/paths.js') + const PORT = configPaths.testPort module.exports = { + browserContext: 'incognito', server: { command: 'node tasks/test-serve.js', launchTimeout: 30000, From ee371691532e1a43205ad2ee9339cfaf78e3627e Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:29:11 +0000 Subject: [PATCH 8/9] Remove Jest retry for flaky tests --- jest.setup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.setup.js b/jest.setup.js index ff92e6c9e2..622a68df2e 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -3,6 +3,5 @@ const { toHaveNoViolations } = require('jest-axe') jest.setTimeout(10000) -jest.retryTimes(3, { logErrorsBeforeRetry: true }) expect.extend(toHaveNoViolations) From ea4ec2bde5c7de443b00fdc500c3eeb4cf3d5e80 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Jan 2023 14:30:01 +0000 Subject: [PATCH 9/9] Set up Jest config files etc Includes CLI flags for colour output, GitHub Actions reporter, ESLint config changes for test globals --- .eslintrc.js | 4 ++++ .github/workflows/test.yaml | 3 +-- config/jest/.eslintrc.js | 5 +++++ jest.setup.js => config/jest/matchers.js | 4 ---- jest.config.mjs | 17 +++++++++++++++++ package.json | 9 --------- 6 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 config/jest/.eslintrc.js rename jest.setup.js => config/jest/matchers.js (64%) create mode 100644 jest.config.mjs diff --git a/.eslintrc.js b/.eslintrc.js index 842755e453..86cf0fa983 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,10 @@ module.exports = { files: ['**/*.test.{cjs,js,mjs}'], env: { jest: true + }, + globals: { + page: true, + browser: true } } ] diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f85e5358a5..5abf1a73b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -41,7 +41,7 @@ jobs: run: npm run build - name: Lint and test - run: npm test -- --runInBand + run: npm test -- --color --maxWorkers=2 # Share data between the build and deploy jobs so we don't need to run `npm run build` again on deploy # Upload the deploy folder as an artifact so it can be downloaded and used in the deploy job @@ -52,4 +52,3 @@ jobs: name: build path: deploy/** retention-days: 1 - diff --git a/config/jest/.eslintrc.js b/config/jest/.eslintrc.js new file mode 100644 index 0000000000..958d51ba27 --- /dev/null +++ b/config/jest/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + jest: true + } +} diff --git a/jest.setup.js b/config/jest/matchers.js similarity index 64% rename from jest.setup.js rename to config/jest/matchers.js index 622a68df2e..905b0e07f4 100644 --- a/jest.setup.js +++ b/config/jest/matchers.js @@ -1,7 +1,3 @@ -/* eslint-env jest */ - const { toHaveNoViolations } = require('jest-axe') -jest.setTimeout(10000) - expect.extend(toHaveNoViolations) diff --git a/jest.config.mjs b/jest.config.mjs new file mode 100644 index 0000000000..faaf285be5 --- /dev/null +++ b/jest.config.mjs @@ -0,0 +1,17 @@ +export default { + preset: 'jest-puppeteer', + + // Custom matchers + setupFilesAfterEnv: ['./config/jest/matchers.js'], + + // Environment defaults for JSDOM + testEnvironmentOptions: { + url: 'https://design-system.service.gov.uk' + }, + + // Enable GitHub Actions reporter UI + reporters: ['default', 'github-actions'], + + // Test timeout increased (5s to 15s) + testTimeout: 15000 +} diff --git a/package.json b/package.json index c56fa98cc7..71136a5cf2 100644 --- a/package.json +++ b/package.json @@ -81,15 +81,6 @@ "node": "^18.12.0", "npm": "^8.1.0 || ^9.1.0" }, - "jest": { - "preset": "jest-puppeteer", - "setupFilesAfterEnv": [ - "./jest.setup.js" - ], - "testEnvironmentOptions": { - "url": "https://design-system.service.gov.uk" - } - }, "browserslist": [ "last 2 versions", "ie 8",