From 838bd68b2bcc1c1ef83e404b8794645d2881d3cf Mon Sep 17 00:00:00 2001 From: Ian Jenkins Date: Mon, 18 Oct 2021 13:48:14 +0100 Subject: [PATCH] [FEATURE] Add support for BackstopJS Visual Regression testing. Add support for easily setting up BackstopJS through docker. Idea is that tests can easily be added via `backstop.json` and then easily run and approved with some basic scripts. Also added a dedicated docs section on testing with a run through of the different testing types supported and how to run each one. --- .gitignore | 1 + README.md | 3 + bin/backstop_report | 17 + bin/docker/backstop | 3 + bin/docker/setup_backstop | 8 + docker-compose.yml | 17 +- docker/backstop/Dockerfile | 40 + docs/dev/testing.md | 83 ++ docs/dev/usage.md | 10 - fixtures.yml | 2 + tests/visual-regression/.gitignore | 6 + tests/visual-regression/backstop.json | 73 ++ tests/visual-regression/bin/backstop.js | 52 + .../config/local/cookies.json.dist | 14 + .../config/local/env_vars.json | 3 + .../config/local/secrets.json.dist | 1 + .../config/profile.json.dist | 3 + tests/visual-regression/package-lock.json | 1140 +++++++++++++++++ tests/visual-regression/package.json | 14 + tests/visual-regression/scripts/envVars.js | 45 + tests/visual-regression/scripts/imageStub.jpg | Bin 0 -> 2900 bytes .../scripts/puppet/clickAndHoverHelper.js | 39 + .../scripts/puppet/ignoreCSP.js | 65 + .../scripts/puppet/interceptImages.js | 37 + .../scripts/puppet/loadCookies.js | 33 + .../scripts/puppet/onBefore.js | 3 + .../scripts/puppet/onReady.js | 6 + .../scripts/puppet/overrideCSS.js | 15 + tests/visual-regression/scripts/utils.js | 80 ++ 29 files changed, 1802 insertions(+), 11 deletions(-) create mode 100755 bin/backstop_report create mode 100755 bin/docker/backstop create mode 100755 bin/docker/setup_backstop create mode 100644 docker/backstop/Dockerfile create mode 100644 docs/dev/testing.md create mode 100644 tests/visual-regression/.gitignore create mode 100644 tests/visual-regression/backstop.json create mode 100644 tests/visual-regression/bin/backstop.js create mode 100644 tests/visual-regression/config/local/cookies.json.dist create mode 100644 tests/visual-regression/config/local/env_vars.json create mode 100644 tests/visual-regression/config/local/secrets.json.dist create mode 100644 tests/visual-regression/config/profile.json.dist create mode 100644 tests/visual-regression/package-lock.json create mode 100644 tests/visual-regression/package.json create mode 100644 tests/visual-regression/scripts/envVars.js create mode 100644 tests/visual-regression/scripts/imageStub.jpg create mode 100644 tests/visual-regression/scripts/puppet/clickAndHoverHelper.js create mode 100644 tests/visual-regression/scripts/puppet/ignoreCSP.js create mode 100644 tests/visual-regression/scripts/puppet/interceptImages.js create mode 100644 tests/visual-regression/scripts/puppet/loadCookies.js create mode 100644 tests/visual-regression/scripts/puppet/onBefore.js create mode 100644 tests/visual-regression/scripts/puppet/onReady.js create mode 100644 tests/visual-regression/scripts/puppet/overrideCSS.js create mode 100644 tests/visual-regression/scripts/utils.js diff --git a/.gitignore b/.gitignore index e2bf5754..4629a13e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ debug.log !/bin/makepot !/bin/pot2po !/bin/po2mo +!/bin/backstop_report .env /.env.local diff --git a/README.md b/README.md index dd837a8d..5398d0d1 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ Admin: 🚩 - First class support for feature flags using [flagpole](https://github.com/jamesrwilliams/flagpole) +✅ - Unit, Integration and Visual Regression testing support + > You can read more about all of these features in [this post on the Box UK blog](https://www.boxuk.com/insight/how-we-develop-wordpress-sites/). @@ -96,6 +98,7 @@ Admin: * [Working with a styleguide](docs/dev/styleguide.md) * [i18n](docs/dev/i18n.md) * [Tools](docs/dev/tools.md) +* [Testing](docs/dev/testing.md) diff --git a/bin/backstop_report b/bin/backstop_report new file mode 100755 index 00000000..b8146655 --- /dev/null +++ b/bin/backstop_report @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +ENVIRONMENT="${@:-local}" + +# TODO: Add Windows support. + +if [ ! -d "tests/visual-regression/output/${ENVIRONMENT}/html_report/" ]; then + exit 1; +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + # Mac OS. + open tests/visual-regression/output/${ENVIRONMENT}/html_report/index.html; +else + # Linux. + xdg-open tests/visual-regression/output/${ENVIRONMENT}/html_report/index.html; +fi diff --git a/bin/docker/backstop b/bin/docker/backstop new file mode 100755 index 00000000..da84ef8f --- /dev/null +++ b/bin/docker/backstop @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +docker-compose -f docker-compose.yml run --rm --workdir /src backstop node ./bin/backstop.js "$@" diff --git a/bin/docker/setup_backstop b/bin/docker/setup_backstop new file mode 100755 index 00000000..a8e9def8 --- /dev/null +++ b/bin/docker/setup_backstop @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +ENVIRONMENT="${@:-local}" + +docker-compose -f docker-compose.yml run --rm --workdir /src backstop cp config/${ENVIRONMENT}/cookies.json.dist config/${ENVIRONMENT}/cookies.json; +docker-compose -f docker-compose.yml run --rm --workdir /src backstop cp config/${ENVIRONMENT}/secrets.json.dist config/${ENVIRONMENT}/secrets.json; + +docker-compose -f docker-compose.yml run --rm --workdir /src backstop npm install; diff --git a/docker-compose.yml b/docker-compose.yml index 29fb1b21..16e4f005 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: context: docker/nginx dockerfile: Dockerfile volumes: - - '.:/var/www/html/' + - '.:/var/www/html:cached' - './docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf' - './docker/nginx/conf:/etc/nginx/conf.d/conf' - './docker/nginx/insecure_https.crt:/etc/pki/tls/certs/insecure_https.crt:delegated' @@ -87,6 +87,21 @@ services: - memcached - -I 5m # Increase item size from the default 1mb to 5mb + backstop: + build: + context: docker/backstop + dockerfile: Dockerfile + volumes: + - './tests/visual-regression:/src:cached' + - vr_yarn_cache:/usr/local/share/.cache/yarn/v6:delegated + - vr_node_modules:/src/app/node_modules:delegated + extra_hosts: + - '${LOOPBACK_HOST_NAME}:192.168.35.10' # IP should match the IP address set for nginx above + +volumes: + vr_yarn_cache: + vr_node_modules: + networks: default: external: true diff --git a/docker/backstop/Dockerfile b/docker/backstop/Dockerfile new file mode 100644 index 00000000..6a0eaa42 --- /dev/null +++ b/docker/backstop/Dockerfile @@ -0,0 +1,40 @@ +FROM node:14.18.1 + +ARG BACKSTOPJS_VERSION + +ENV \ + BACKSTOPJS_VERSION=$BACKSTOPJS_VERSION + +# Base packages +RUN apt-get update && \ + apt-get install -y git sudo software-properties-common --no-install-recommends; \ + rm -rf /var/lib/apt/lists/* + +#RUN sudo npm install -g --unsafe-perm=true --allow-root backstopjs@${BACKSTOPJS_VERSION} + +RUN wget --progress=dot:giga https://dl-ssl.google.com/linux/linux_signing_key.pub && apt-key add linux_signing_key.pub; \ + add-apt-repository "deb http://dl.google.com/linux/chrome/deb/ stable main" + +# RUN apt-get -y update && apt-get -y install google-chrome-stable + +# RUN apt-get install -y firefox-esr + +# gconf-service libxext6.... added for https://github.com/garris/BackstopJS/issues/1225 + +RUN apt-get -qqy update \ + && apt-get -qqy --no-install-recommends install \ + libfontconfig \ + libfreetype6 \ + xfonts-cyrillic \ + xfonts-scalable \ + fonts-liberation \ + fonts-ipafont-gothic \ + fonts-wqy-zenhei \ + gconf-service libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxss1 libxtst6 libappindicator1 libnss3 libasound2 libatk1.0-0 libc6 ca-certificates fonts-liberation lsb-release xdg-utils wget \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get -qyy clean + + +WORKDIR /src + +#ENTRYPOINT ["backstop"] diff --git a/docs/dev/testing.md b/docs/dev/testing.md new file mode 100644 index 00000000..54b59eed --- /dev/null +++ b/docs/dev/testing.md @@ -0,0 +1,83 @@ +# Testing + +Testing WordPress sites can be a challenge. We should strive to make use of different testing techniques to ensure we're getting good test coverage across our sites. Here are the different types of tests we recommend and how to achieve each. + +* [Unit Testing](#unit-testing) +* [Integration Testing](#integration-testing) +* [Visual Regression Testing](#visual-regression-testing) +* [Acceptance Testing](#acceptance-testing) + +## Unit Testing + +Unit tests should be added for every `(mu-)plugin`/`theme` we create. We should always aim to decouple the core logic from WordPress, for this to be true we should avoid any use of global functions or hidden dependencies in any classes/functions we use for our core logic. We should then be able to unit test these easily. + +We use [PHPUnit](https://phpunit.de/) for unit testing. + +## Integration Testing + +Integration tests should also be added wherever appropriate. For any part of your `(mu-)plugin`/`theme` that interacts with WordPress this should be covered by an integration test. + +We also use [PHPUnit](https://phpunit.de/) for integration testing along with [WP PHPUnit](https://github.com/wp-phpunit/wp-phpunit) which will allow you to interact with the WordPress install easily. It also provides a number of helpers to allow for easy generation of synthetic data for testing. + +### Running the unit & integration tests + +Both the unit tests and integration tests follow the same structure, tests are designed to be added within each `mu-plugin` you create within a `tests` directory. The test runner sits outside though in the root of this repo. It works by looping over each `mu-plugin` and running its test. + +> It's important each `mu-plugin` follows the format `plugin-name/plugin-name.php` + +`bin/docker/phpunit` + +(or `bin/phpunit` if not using docker) + +## Visual Regression Testing + +Sometimes hard to find bugs can be present even though all our automated tests are passing, this could be due to something that's fallen through the cracks, a JS/CSS issue, an update to a plugin or theme, an update to a browser or something else entirely. It's therefore useful to run Visual Regression (VR) tests to catch anything that may have changed how the site appears to the end user. + +VR tests work by taking screenshots of given scenarios (typically defined pages/posts), approving the screenshots to a known state and then comparing future runs against this approved state to see if anything has changed. Tests will then fail if the difference between the screenshots is above a set tolerance. + +We use [BackstopJS](https://github.com/garris/BackstopJS/) for VR testing. + +### Running the tests + +Tests (or scenarios) should be added to `tests/visual-regression/backstop.json` within the `scenarios` section. For example: + +```json + "scenarios": [ + { + "label": "Page or post to test", + "cookiePath": "config/%env_name%/cookies.json", + "url": "%base_url%/page-or-post-slug", + "delay": 500, + "misMatchThreshold": 0.1, + "requireSameDimensions": true + } + ] +``` + +You can configure things like cookies, env vars and secrets within the `config` directory. You can do so for each environment you want to test, `local` is set up currently that will allow you to test the local environment. + +* Setup backstop + +`bin/docker/setup_backstop` + +* Run backstop tests + +`bin/docker/backstop test [environment]` + +* Approve detected changes + +`bin/docker/backstop approve [environment]` + +> [environment] is the environment you want to test against, falls back to `local`. + +* Open report + +`bin/backstop_report` + +> This will open the test report in your default browser. + +## Acceptance testing + +As well as the other tests we also want to run Acceptance Tests, so we can test our `(mu-)plugin`/`theme` does what it's supposed to do for the end user. For this we typically want to run some automated browser based testing to make sure clicking links, navigating between sections works the way it should. + +We use... [coming soon] diff --git a/docs/dev/usage.md b/docs/dev/usage.md index c4e29c5b..1a333cf1 100644 --- a/docs/dev/usage.md +++ b/docs/dev/usage.md @@ -8,16 +8,6 @@ This one is optional but highly recommended, if you want to use memcache for obj `cp wp-content/plugins/memcached/object-cache.php wp-content/object-cache.php` -## Running the tests - -Tests are designed to be added within each `mu-plugin` you create within a `tests` directory. The test runner sits outside though in the root of this repo. It works by looping over each `mu-plugin` and running it's test. - -> It's important each `mu-plugin` follows the format `plugin-name/plugin-name.php` - -`bin/docker/phpunit` - -(or `bin/phpunit` if not using docker) - ## Running the code sniffs We use the [WordPress VIP coding standards](https://github.com/Automattic/VIP-Coding-Standards) to ensure the code is adhering to the best possible performance and security practices. We also use the core [WordPress standards](https://github.com/WordPress/WordPress-Coding-Standards) and the [Neutron standards](https://github.com/Automattic/phpcs-neutron-standard) to ensure for consistency for a modern WordPress code base. You can check your code against all of this by running the following command: diff --git a/fixtures.yml b/fixtures.yml index a23e261d..956eb6e2 100644 --- a/fixtures.yml +++ b/fixtures.yml @@ -58,6 +58,8 @@ Hellonico\Fixtures\Entity\Post: post_category: '1x @category*->term_id' tax_input: post_tag: '5x @tag*->term_id' + post_sample (extends default): + post_title: Sample Post ## Menu Hellonico\Fixtures\Entity\NavMenu: diff --git a/tests/visual-regression/.gitignore b/tests/visual-regression/.gitignore new file mode 100644 index 00000000..a751f4ed --- /dev/null +++ b/tests/visual-regression/.gitignore @@ -0,0 +1,6 @@ +node_modules +output +!output/.gitkeep +config/**/cookies.json +config/**/secrets.json +config/profile.json diff --git a/tests/visual-regression/backstop.json b/tests/visual-regression/backstop.json new file mode 100644 index 00000000..ed5b53bc --- /dev/null +++ b/tests/visual-regression/backstop.json @@ -0,0 +1,73 @@ +{ + "docker": "%use_docker%", + "config": { + "id": "backstop_default", + "viewports": [ + { + "label": "phone", + "width": 320, + "height": 480 + }, + { + "label": "tablet", + "width": 1024, + "height": 768 + }, + { + "label": "desktop", + "width": 1920, + "height": 1080 + } + ], + "onBeforeScript": "puppet/onBefore.js", + "onReadyScript": "puppet/onReady.js", + "scenarios": [ + { + "label": "Homepage", + "cookiePath": "config/%env_name%/cookies.json", + "url": "%base_url%/", + "delay": 500, + "misMatchThreshold": 0.1, + "requireSameDimensions": true + }, + { + "label": "About page", + "cookiePath": "config/%env_name%/cookies.json", + "url": "%base_url%/about", + "delay": 500, + "misMatchThreshold": 0.1, + "requireSameDimensions": true + }, + + { + "label": "Sample post", + "cookiePath": "config/%env_name%/cookies.json", + "url": "%base_url%/sample-post", + "delay": 500, + "misMatchThreshold": 0.1, + "requireSameDimensions": true + } + ], + "paths": { + "bitmaps_reference": "output/%env_name%/bitmaps_reference", + "bitmaps_test": "output/%env_name%/bitmaps_test", + "engine_scripts": "scripts", + "html_report": "output/%env_name%/html_report", + "ci_report": "output/%env_name%/ci_report" + }, + "report": [ + "html" + ], + "engine": "puppeteer", + "engineOptions": { + "ignoreHTTPSErrors": true, + "args": [ + "--no-sandbox" + ] + }, + "asyncCaptureLimit": 5, + "asyncCompareLimit": 50, + "debug": false, + "debugWindow": false + } +} diff --git a/tests/visual-regression/bin/backstop.js b/tests/visual-regression/bin/backstop.js new file mode 100644 index 00000000..f20dc857 --- /dev/null +++ b/tests/visual-regression/bin/backstop.js @@ -0,0 +1,52 @@ +const backstop = require('backstopjs'); +const envVars = require('../scripts/envVars.js'); +const utils = require('../scripts/utils.js'); +const { exec } = require("child_process"); + +const validOperations = [ 'test', 'approve' ]; + +const myArgs = process.argv.slice( 2 ); +const operation = myArgs[ 0 ]; +const env = myArgs[ 1 ] || 'local'; + +const configFolderPath = 'config/'; +const envVarsFilePath = configFolderPath + env + '/env_vars.json'; +const envSecretsFilePath = configFolderPath + env + '/secrets.json'; +const profilePath = configFolderPath + 'profile.json'; +const profilePathFallback = configFolderPath + 'profile.json.dist'; +const backstopFilePath = 'backstop.json'; + +if ( ! utils.isOperationValid( operation, validOperations ) ) { + utils.exitWithReason( '"' + operation + '" is not a valid operation. Valid operations are: ' + validOperations.join( ', ' ) ); +} + +if ( ! utils.doesFileExist( envVarsFilePath ) ) { + utils.exitWithReason( 'Required file not found at ' + envVarsFilePath ); +} + +if ( ! utils.doesFileExist( envSecretsFilePath ) ) { + utils.exitWithReason( 'Required file not found at ' + envSecretsFilePath ); +} + +if ( ! utils.doesFileExist( backstopFilePath ) ) { + utils.exitWithReason( 'Required file not found at ' + backstopFilePath ); +} + +console.log( 'Using environment variables file ' + envVarsFilePath ); +console.log( 'Using environment secrets file ' + envSecretsFilePath ); +console.log( 'Using backstop file ' + backstopFilePath ); + +envVars.setVars( utils.parseJsonFile( envVarsFilePath ) ); +envVars.setVars( utils.parseJsonFile( envSecretsFilePath ) ); + +const profilePathToUse = utils.doesFileExist( profilePath ) ? profilePath : profilePathFallback; +console.log( 'Using profile file ' + profilePathToUse ); + +envVars.setVars( utils.parseJsonFile( profilePathToUse ) ); + +// Add env_name to the configuration object, so it can be referenced from within the config. +envVars.setVar( 'env_name', env ); + +const config = utils.parseJsonFile( backstopFilePath, envVars.getVars() ); + +backstop( operation, config ); diff --git a/tests/visual-regression/config/local/cookies.json.dist b/tests/visual-regression/config/local/cookies.json.dist new file mode 100644 index 00000000..44829019 --- /dev/null +++ b/tests/visual-regression/config/local/cookies.json.dist @@ -0,0 +1,14 @@ +[ + { + "domain": ".boxuk-wp-skeleton.local", + "path": "/", + "name": "yourCookieName", + "value": "yourCookieValue", + "expirationDate": 1798790400, + "hostOnly": false, + "httpOnly": false, + "secure": false, + "session": false, + "sameSite": "no_restriction" + } +] diff --git a/tests/visual-regression/config/local/env_vars.json b/tests/visual-regression/config/local/env_vars.json new file mode 100644 index 00000000..8f203562 --- /dev/null +++ b/tests/visual-regression/config/local/env_vars.json @@ -0,0 +1,3 @@ +{ + "base_url": "https://boxuk-wp-skeleton.local" +} diff --git a/tests/visual-regression/config/local/secrets.json.dist b/tests/visual-regression/config/local/secrets.json.dist new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/visual-regression/config/local/secrets.json.dist @@ -0,0 +1 @@ +{} diff --git a/tests/visual-regression/config/profile.json.dist b/tests/visual-regression/config/profile.json.dist new file mode 100644 index 00000000..8dd81426 --- /dev/null +++ b/tests/visual-regression/config/profile.json.dist @@ -0,0 +1,3 @@ +{ + "use_docker": false +} diff --git a/tests/visual-regression/package-lock.json b/tests/visual-regression/package-lock.json new file mode 100644 index 00000000..a7ba2f06 --- /dev/null +++ b/tests/visual-regression/package-lock.json @@ -0,0 +1,1140 @@ +{ + "name": "boxuk-wordpress-project-visual-regression", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@mirzazeyrek/node-resemble-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mirzazeyrek/node-resemble-js/-/node-resemble-js-1.2.1.tgz", + "integrity": "sha512-+z1c7HpC5ysdSVVyUVz67hctVLl337VlRJP/MBwpvXHkKJdlnSUVrBhlRzxgal7xpm1uDE2JeUhWbQh6wPRC4w==", + "requires": { + "jpeg-js": "^0.4.2", + "pngjs": "^6.0.0" + } + }, + "@types/node": { + "version": "16.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", + "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", + "optional": true + }, + "@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "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==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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==" + } + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "backstopjs": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/backstopjs/-/backstopjs-5.3.7.tgz", + "integrity": "sha512-rnYK4zNsmIfWrBmVfVgVw2W9HgMpaojsedcKweNwNz+8YfRDZCOQGn+ns8daS4YsxjX7mkgtiyhRfa8V5rNvEA==", + "requires": { + "@mirzazeyrek/node-resemble-js": "^1.2.1", + "chalk": "^1.1.3", + "diverged": "^0.1.3", + "fs-extra": "^0.30.0", + "jump.js": "^1.0.2", + "junit-report-builder": "^1.3.3", + "lodash": "^4.17.11", + "minimist": "^1.2.0", + "object-hash": "1.1.5", + "opn": "^5.3.0", + "os": "^0.1.1", + "p-map": "^1.1.1", + "path": "^0.12.7", + "portfinder": "^1.0.17", + "puppeteer": "^10.0.0", + "super-simple-web-server": "^1.1.2", + "temp": "^0.8.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "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==" + } + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "date-format": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz", + "integrity": "sha1-+v1Ej3IRXvHitzkVWukvK+bCjdE=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "devtools-protocol": { + "version": "0.0.901419", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", + "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "diverged": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/diverged/-/diverged-0.1.3.tgz", + "integrity": "sha512-W8BLyp4Eo+YW9uQ3F5c9BXDT9ITCARA2CFQVb+v57FWYfkr0XjwNOASZacDCq+syk1i/obZ4BZ3w1qtlRO6hQw==", + "requires": { + "diff": "^3.5.0", + "pixelmatch": "^4.0.2", + "pngjs": "^3.3.3", + "super-simple-web-server": "^1.0.0" + }, + "dependencies": { + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + } + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "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==", + "requires": { + "once": "^1.4.0" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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==" + } + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "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==" + }, + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "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==", + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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==" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "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==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "jpeg-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", + "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==" + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jump.js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jump.js/-/jump.js-1.0.2.tgz", + "integrity": "sha1-4GQbR/QKOPITnCX9oFAL8o5DAVo=" + }, + "junit-report-builder": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.3.3.tgz", + "integrity": "sha512-75bwaXjP/3ogyzOSkkcshXGG7z74edkJjgTZlJGAyzxlOHaguexM3VLG6JyD9ZBF8mlpgsUPB1sIWU4LISgeJw==", + "requires": { + "date-format": "0.0.2", + "lodash": "^4.17.15", + "mkdirp": "^0.5.0", + "xmlbuilder": "^10.0.0" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + }, + "mime-types": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "requires": { + "mime-db": "1.50.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "object-hash": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.1.5.tgz", + "integrity": "sha1-vdhE4DDQhhtpLKF1xsq2ho7CM9c=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "os": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz", + "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "^3.0.0" + }, + "dependencies": { + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + } + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==" + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", + "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "puppeteer": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz", + "integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==", + "requires": { + "debug": "4.3.1", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.1", + "pkg-dir": "4.2.0", + "progress": "2.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.0.0", + "unbzip2-stream": "1.3.3", + "ws": "7.4.6" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "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==" + }, + "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" + } + } + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "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==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "super-simple-web-server": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/super-simple-web-server/-/super-simple-web-server-1.1.2.tgz", + "integrity": "sha512-18yaU4aGj24bRnk2HLQxXepV53SDQI7xWaoVoxb8U0ujOm/wbC+IY7wmuT6fV24qOlxqmqpVbjaKfw25NjRr0w==", + "requires": { + "express": "^4.16.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "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" + } + }, + "temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "requires": { + "rimraf": "~2.6.2" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unbzip2-stream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", + "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + }, + "xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/tests/visual-regression/package.json b/tests/visual-regression/package.json new file mode 100644 index 00000000..49aaf73d --- /dev/null +++ b/tests/visual-regression/package.json @@ -0,0 +1,14 @@ +{ + "name": "boxuk-wordpress-project-visual-regression", + "version": "1.0.0", + "description": "Visual regression testing for a WordPress project", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "GPL-2.0-or-later", + "dependencies": { + "backstopjs": "^5.3.7" + } +} diff --git a/tests/visual-regression/scripts/envVars.js b/tests/visual-regression/scripts/envVars.js new file mode 100644 index 00000000..26f884db --- /dev/null +++ b/tests/visual-regression/scripts/envVars.js @@ -0,0 +1,45 @@ +module.exports = { + vars: {}, + + /** + * Bulk set variables. + * + * @param {{}} vars Object containing variables. + */ + setVars: function(vars) { + for(let key in vars) { + if (vars.hasOwnProperty(key)) { + this.setVar(key, vars[key]); + } + } + }, + + /** + * Bulk return the variables. + * + * @returns {{}} + */ + getVars: function() { + return this.vars; + }, + + /** + * Set a variable. + * + * @param {string} key Variable name. + * @param {*} value Variable value. + */ + setVar: function(key, value) { + this.vars[key] = value; + }, + + /** + * Retrieve a variable. + * + * @param {string} key Variable name + * @returns {*} + */ + getVar: function(key) { + return this.vars[key]; + } +}; diff --git a/tests/visual-regression/scripts/imageStub.jpg b/tests/visual-regression/scripts/imageStub.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e526b4bbbc74ef5125cb58f7c8849497ef4fa83 GIT binary patch literal 2900 zcmcIlX*3(?8csxzq9yjys3n$asUT`8Q(MLo#@bS?(O4Q=8>Mz#RNGi45oa_YifWe%c;6vQpTs(Y-`1p7a@$&K?h6?Z>7Cg+$DOk7l094ag#d;kIjvDa{dAz&~>n4gzl_>S*Qq_R8DHH~mACV-F4m_raG2rvW;gVs)O z{Ps`k|C)7Pt-8e3!0!4}l?X&=o#d)OtX~v&*`qf5S8E9O73Ewz!sB?~J=wG~^cZiv zPVAx1pt19L^>AfrTWy9UYISWNu`R8;vZi}_mR|bv)41<;&Kd|s31?-6DC$Yo?9s4Y zz3eG9g^F8?`I=jluHy!`xpke)?ZF#mvhO61$Zcl?uft85W*9lAcipr@9r@(!{SDE< zb-Njn##b$$gQcgmSbzdlzvYH?&+e}*K>Q!gY^SwOk@ep`49iKK$5lc}*;~Y<)?wF$ zjFRR)*S_(NzCq%&;OzsEpyN0LlknW-8{Ti|c0nQIzJqEQ{n-^yA=oI#Eyk-6E3TNr295F5b~l>m4>GbC-;qCN^7Y4OuydL z#otg+>27!THP0C!TUgXLSw3nDnw{?pFleC{%)M?`EQpjwURlC+SD5@*L*p9Dhm=;l zEG)W3d(ljnS++@>2~U4iSc?CNVgAE`HolnVZ!Q7fkdb`Bil}IAY1g_0q+6)t$|}w0 zCJW%5i4~Qb*{qX%M{+D%hqa#7!Cbn=yeDaz)lwn_m&bold2rcV^UDZ^fmkY(vP;5W zchD(FsGKh46Ou_BXy@E8&n_;nH}sRa#$o!oAH5Ld*tF(^_$TP2ek6wJ zoY7K=8Va6Yxc*VB`emcilzB0!`91}%f^AJ8Cx4nnNHzW%QB#FL)}t#Xt!)FtwZtfh zV9%*M`PqgW<)1CxTac4zUh_q)t)GYf!fpi4KSjHx6*t~D-nVBt*N#>;T_B8KOgV7K z1Exsp3#oZEj>*^6`+8<)X3@R;`&;D94mgh)e?3IJRpMHhQ*LjMWvtY|aIs-m=03dU z@atH3Vuvx251=VJG^@@vnQsafH?>7C zq(}>`-xLZ?9h;S*tyy3bQ|~RIcI6Ybb#fwD0Ir_9S#R8?pQ)C2_yx8zzgsGO_13DU zy3edSWp)OR*7C)QOuG3we2&2;cPxzWl&2;v^)(WcoL;%Rbj&t|CX7UpW@J22Ws6bN zE6g?;bwZ0`Qs(1Z3{%?Se`Y%tPkCKyBZGeZFxSF}X89%0Up%uJeNioT^{mwcyuV8G z(yaZccYknI(kPN@LR2D6S*nur>RJ&e@+%8WpLaSLJEux7$6>pM9lQ2VhT#|;{{7NM zr07*C<$63l^p*Xt-@5*htx6VPVoNi7m+dO(_{!OVjJobD&@uC#k9X*QTq&`S5N6?n z$)jeBr~#Cl^O+Y06LiuFJTe(Ew7E+!pB&*iPfahHanu7aF z&HI9A!2*<@GDr-W@RTSx&lpz4BAN;?8WU&kw;w4ju@m+n$qlgpa!0FzD0-vW8?O9W zhoG?#^Q&FpW`$JGZCLTxkmFR9(6idPI>|+Zk5Ls?K0+ERK6=wBKsG0i=xzbZ4STJ=T@!;qNKobcQV z1E*;1XDzf!8sBI9P}Z(8(d}MhS){uHO^Qfg^xSLphVD>I9H^D2C4w%h%qX?YMYc^| zG+irV0WPmSiI-|Qxs+E_lqP%Z9+KbRV3_DfE&Slx6jE0U65QoolaZjuSi9QGTyQ?+ zQ|R@1;sYsK`}vmVB`WIY3oS2=ueC<$r&MKY%p$&tPoDbPLD3G?K&a>DK+qL~3Ju0# zZ|^-P@U&m;J$deh*lqU4NO0qqD#n)lw!{~#f7v~3P!l2USN}C1l`HBUPFXVlqRzRU zj3WBDS0~9}7BzQsnb*n1y_8S$qDbo#E=bS3#&%kz?l@CVF~vcCQ)iL5`ADsz(zLF! z15aCY(Tolsw%Lj?+`Lj!xGKvKd5VVTr}e=+J*F%#&)Wt`t3#$51?s&@drL}iOLJN3 zZMA(V(@LQ;_it5LKrR#--Pa@I&A7q6$( z9<7hTZ+D$-#TjhHk^3KGVB`o!im` zhY+sr>w(A4EmD{3o2VPg)4pF$EPR43;n$?_bq3lg3tm@Bn&8Hc@PE)j91$AB){YK; zRqxL{wj~u6xOsXwYQm0Sb+R37IBL&~mW8ar39_9nh&W~k64Ms>+XC7QJ1w!E^>Y>Z zVkBg;*QAPX)-OBTBzTp1`MZjL^`{}TfhQ7*8bi`RThIO8_uEfRROQ<4yLNs8FczJX zd0*T?G&56RIx@}SB2Ve>mu01vtE*9dUSF&d9xD$(ld+#+ZZ%0Ib7l~vix!et@)`5~ z{->OH8L6tlvecO&Oy}x~e^Y(vV6mbY#VtSRF^K^isxY|CR`Va<*`^%WBj@DE^CD%H zF9J-wNL7gzvKr1}=i+_k$@mG;p1}?1)IR5}IU@s_%0hWW!Vlr)Bz-JIp7x8=G^)Wo z{h`N(N8w%F?Bw~ssc8orB+h78;{0UZV%y3EtdW!`d9O?NG3c(3#|SY*ecweNT)1KR zN9ctQ;lM%(*5g7(ty2+FV5TWF9tEAemCv4|q(?hW~Wkr#L`lhjCSNuCe&85{WjhJ1@P{5z{dH?#l% literal 0 HcmV?d00001 diff --git a/tests/visual-regression/scripts/puppet/clickAndHoverHelper.js b/tests/visual-regression/scripts/puppet/clickAndHoverHelper.js new file mode 100644 index 00000000..81ff30e8 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/clickAndHoverHelper.js @@ -0,0 +1,39 @@ +module.exports = async (page, scenario) => { + const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector; + const clickSelector = scenario.clickSelectors || scenario.clickSelector; + const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector; + const scrollToSelector = scenario.scrollToSelector; + const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int] + + if (keyPressSelector) { + for (const keyPressSelectorItem of [].concat(keyPressSelector)) { + await page.waitFor(keyPressSelectorItem.selector); + await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress); + } + } + + if (hoverSelector) { + for (const hoverSelectorIndex of [].concat(hoverSelector)) { + await page.waitFor(hoverSelectorIndex); + await page.hover(hoverSelectorIndex); + } + } + + if (clickSelector) { + for (const clickSelectorIndex of [].concat(clickSelector)) { + await page.waitFor(clickSelectorIndex); + await page.click(clickSelectorIndex); + } + } + + if (postInteractionWait) { + await page.waitFor(postInteractionWait); + } + + if (scrollToSelector) { + await page.waitFor(scrollToSelector); + await page.evaluate(scrollToSelector => { + document.querySelector(scrollToSelector).scrollIntoView(); + }, scrollToSelector); + } +}; diff --git a/tests/visual-regression/scripts/puppet/ignoreCSP.js b/tests/visual-regression/scripts/puppet/ignoreCSP.js new file mode 100644 index 00000000..02253bcc --- /dev/null +++ b/tests/visual-regression/scripts/puppet/ignoreCSP.js @@ -0,0 +1,65 @@ +/** + * IGNORE CSP HEADERS + * Listen to all requests. If a request matches scenario.url + * then fetch the request again manually, strip out CSP headers + * and respond to the original request without CSP headers. + * Allows `ignoreHTTPSErrors: true` BUT... requires `debugWindow: true` + * + * see https://github.com/GoogleChrome/puppeteer/issues/1229#issuecomment-380133332 + * this is the workaround until Page.setBypassCSP lands... https://github.com/GoogleChrome/puppeteer/pull/2324 + * + * @param {REQUEST} request + * @return {VOID} + * + * Use this in an onBefore script E.G. + ``` + module.exports = async function(page, scenario) { + require('./removeCSP')(page, scenario); + } + ``` + * + */ + +const fetch = require('node-fetch'); +const https = require('https'); +const agent = new https.Agent({ + rejectUnauthorized: false +}); + +module.exports = async function (page, scenario) { + const intercept = async (request, targetUrl) => { + const requestUrl = request.url(); + + // FIND TARGET URL REQUEST + if (requestUrl === targetUrl) { + const cookiesList = await page.cookies(requestUrl); + const cookies = cookiesList.map(cookie => `${cookie.name}=${cookie.value}`).join('; '); + const headers = Object.assign(request.headers(), { cookie: cookies }); + const options = { + headers: headers, + body: request.postData(), + method: request.method(), + follow: 20, + agent + }; + + const result = await fetch(requestUrl, options); + + const buffer = await result.buffer(); + let cleanedHeaders = result.headers._headers || {}; + cleanedHeaders['content-security-policy'] = ''; + await request.respond({ + body: buffer, + headers: cleanedHeaders, + status: result.status + }); + } else { + request.continue(); + } + }; + + await page.setRequestInterception(true); + page.on('request', req => { + intercept(req, scenario.url); + }); +}; diff --git a/tests/visual-regression/scripts/puppet/interceptImages.js b/tests/visual-regression/scripts/puppet/interceptImages.js new file mode 100644 index 00000000..c6c5ed39 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/interceptImages.js @@ -0,0 +1,37 @@ +/** + * INTERCEPT IMAGES + * Listen to all requests. If a request matches IMAGE_URL_RE + * then stub the image with data from IMAGE_STUB_URL + * + * Use this in an onBefore script E.G. + ``` + module.exports = async function(page, scenario) { + require('./interceptImages')(page, scenario); + } + ``` + * + */ + +const fs = require('fs'); +const path = require('path'); + +const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i; +const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg'); +const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL); +const HEADERS_STUB = {}; + +module.exports = async function (page, scenario) { + const intercept = async (request, targetUrl) => { + if (IMAGE_URL_RE.test(request.url())) { + await request.respond({ + body: IMAGE_DATA_BUFFER, + headers: HEADERS_STUB, + status: 200 + }); + } else { + request.continue(); + } + }; + await page.setRequestInterception(true); + page.on('request', intercept); +}; diff --git a/tests/visual-regression/scripts/puppet/loadCookies.js b/tests/visual-regression/scripts/puppet/loadCookies.js new file mode 100644 index 00000000..85b90cd3 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/loadCookies.js @@ -0,0 +1,33 @@ +const fs = require('fs'); + +module.exports = async (page, scenario) => { + let cookies = []; + const cookiePath = scenario.cookiePath; + + // READ COOKIES FROM FILE IF EXISTS + if (fs.existsSync(cookiePath)) { + cookies = JSON.parse(fs.readFileSync(cookiePath)); + } + + // MUNGE COOKIE DOMAIN + cookies = cookies.map(cookie => { + if (cookie.domain.startsWith('http://') || cookie.domain.startsWith('https://')) { + cookie.url = cookie.domain; + } else { + cookie.url = 'https://' + cookie.domain; + } + delete cookie.domain; + return cookie; + }); + + // SET COOKIES + const setCookies = async () => { + return Promise.all( + cookies.map(async (cookie) => { + await page.setCookie(cookie); + }) + ); + }; + await setCookies(); + console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2)); +}; diff --git a/tests/visual-regression/scripts/puppet/onBefore.js b/tests/visual-regression/scripts/puppet/onBefore.js new file mode 100644 index 00000000..a1c374c3 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/onBefore.js @@ -0,0 +1,3 @@ +module.exports = async (page, scenario, vp) => { + await require('./loadCookies')(page, scenario); +}; diff --git a/tests/visual-regression/scripts/puppet/onReady.js b/tests/visual-regression/scripts/puppet/onReady.js new file mode 100644 index 00000000..517c0e41 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/onReady.js @@ -0,0 +1,6 @@ +module.exports = async (page, scenario, vp) => { + console.log('SCENARIO > ' + scenario.label); + await require('./clickAndHoverHelper')(page, scenario); + + // add more ready handlers here... +}; diff --git a/tests/visual-regression/scripts/puppet/overrideCSS.js b/tests/visual-regression/scripts/puppet/overrideCSS.js new file mode 100644 index 00000000..8d0b9dd7 --- /dev/null +++ b/tests/visual-regression/scripts/puppet/overrideCSS.js @@ -0,0 +1,15 @@ +const BACKSTOP_TEST_CSS_OVERRIDE = `html {background-image: none;}`; + +module.exports = async (page, scenario) => { + // inject arbitrary css to override styles + await page.evaluate(`window._styleData = '${BACKSTOP_TEST_CSS_OVERRIDE}'`); + await page.evaluate(() => { + const style = document.createElement('style'); + style.type = 'text/css'; + const styleNode = document.createTextNode(window._styleData); + style.appendChild(styleNode); + document.head.appendChild(style); + }); + + console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label); +}; diff --git a/tests/visual-regression/scripts/utils.js b/tests/visual-regression/scripts/utils.js new file mode 100644 index 00000000..8bd80737 --- /dev/null +++ b/tests/visual-regression/scripts/utils.js @@ -0,0 +1,80 @@ +const fs = require('fs'); + +module.exports = { + /** + * Returns whether the file exists at the specified path. + * + * @param {string} path The path to validate. + * @returns {boolean} + */ + doesFileExist: function(path) { + return fs.existsSync(path); + }, + + /** + * Returns whether a specified operation is valid. + * + * @param {string} operation The operation to validate. + * @param {array} validOperations array of operations which are considered valid. + * + * @returns {boolean} + */ + isOperationValid: function(operation, validOperations) { + return validOperations.indexOf(operation) !== -1 + }, + + /** + * Exit the process and display the reason in the console. + * + * @param {string} reason The reason for the early exit. + */ + exitWithReason: function(reason) { + console.log(reason); + process.exit(); + }, + + /** + * Read file contents of JSON file and return a JSON object. + * + * If parameters object is passed, they will be injected into the JSON object before it is returned. Parameters used + * in the JSON file being parsed must be in the format %my_parameter%. + * + * @param {string} path + * @param {{}} parameters Reviver function. + */ + parseJsonFile: function(path, parameters = {}) { + return JSON.parse( + fs.readFileSync(path).toString(), function(key, value) { + if (typeof value !== 'string') { + return value; + } + + // If no parameters are provided, no need to inject them into the results. + if (Object.entries(parameters).length === 0) { + return value; + } + + let replacementToMake = false; + + let result = value.replace(/%.*?%/g, function(old) { + let key = old.split("%").join(""); + replacementToMake = parameters.hasOwnProperty(key); + + return replacementToMake ? parameters[key] : old + }); + + // Ensure we don't transform our boolean variables into strings. + if (replacementToMake && result === 'true') { + return true; + } + + // Ensure we don't transform our boolean variables into strings. + if (replacementToMake && result === 'false') { + return false; + } + + return result; + } + ); + } +};