Skip to content

Commit 06c66bc

Browse files
committed
Build Node app around BackstopJS
0 parents  commit 06c66bc

21 files changed

+6952
-0
lines changed

.babelrc

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"presets": [
3+
[
4+
"@babel/preset-env",
5+
{
6+
"targets": {
7+
"node": "current"
8+
}
9+
}
10+
]
11+
]
12+
}

.eslintrc.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"parserOptions": {
3+
"ecmaVersion": 6,
4+
"sourceType": "module"
5+
},
6+
"env": {
7+
"node": true
8+
},
9+
"extends": "eslint:recommended",
10+
"rules": {
11+
"semi": 2,
12+
"no-unused-vars": 2,
13+
"no-console": 0
14+
}
15+
}

.gitignore

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#Backstop JS
2+
backstop_data
3+
4+
# Logs
5+
logs
6+
*.log
7+
npm-debug.log*
8+
yarn-debug.log*
9+
yarn-error.log*
10+
11+
# Runtime data
12+
pids
13+
*.pid
14+
*.seed
15+
*.pid.lock
16+
17+
# Directory for instrumented libs generated by jscoverage/JSCover
18+
lib-cov
19+
20+
# Coverage directory used by tools like istanbul
21+
coverage
22+
23+
# nyc test coverage
24+
.nyc_output
25+
26+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27+
.grunt
28+
29+
# Bower dependency directory (https://bower.io/)
30+
bower_components
31+
32+
# node-waf configuration
33+
.lock-wscript
34+
35+
# Compiled binary addons (http://nodejs.org/api/addons.html)
36+
build/Release
37+
38+
# Dependency directories
39+
node_modules/
40+
jspm_packages/
41+
42+
# Typescript v1 declaration files
43+
typings/
44+
45+
# Optional npm cache directory
46+
.npm
47+
48+
# Optional eslint cache
49+
.eslintcache
50+
51+
# Optional REPL history
52+
.node_repl_history
53+
54+
# Output of 'npm pack'
55+
*.tgz
56+
57+
# Yarn Integrity file
58+
.yarn-integrity
59+
60+
# dotenv environment variables file
61+
.env
62+

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Andrew Taylor
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Automating your QA with Visual Regression Testing Example Repository
2+
3+
This repository is an example for my talk [Automating your QA with Visual Regression Testing](https://2019.europe.wordcamp.org/session/automating-your-qa-with-visual-regression-testing/) at WordCamp Europe 2019. It uses [BackstopJS](https://github.com/garris/BackstopJS/) and [Node JS](https://nodejs.org/) to automate visual QA. The slides for the talk can be found [here](https://goo.gl/V7QtNw).
4+
5+
## Prerequisites
6+
7+
You will need:
8+
9+
* A local development environment with [Node JS/NPM](https://docs.npmjs.com/getting-started/installing-node)
10+
* [Google Chrome](https://www.google.com/chrome/)
11+
* A live, web-accessible WordPress site
12+
* Another environment of the WordPress site above (e.g. local, staging, etc.)
13+
14+
### Getting The Code
15+
16+
Either clone this repository using Git or download the [`master` branch `.zip` file](https://github.com/ataylorme/wordcamp-europe-2019-visual-regression-testing-workshop/archive/master.zip).
17+
18+
## Instructions
19+
20+
After setting up the repository locally (see above) you will need to:
21+
22+
1. Run the command `npm install` to download dependencies
23+
1. Run the command `npm run start`
24+
* Type the number of a site from the list or enter _all_ to test all sites in `src/sitesToTest.js`
25+
1. Check out the results from the sample test
26+
* They should open in your browser automatically
27+
1. Edit `src/sitesToTest.js`
28+
* This is where the list of sites to test is stored
29+
* Try changing to one (or more) of your sites
30+
* `BackstopReferenceBaseUrl` is your non-production environment (local, staging, etc.) URL
31+
* `BackstopTestUrl` is your production site URL
32+
* Adjust `pathsToTest`, which is the array of URIs to test for each site
33+
1. Edit `src/backstopConfig.js` to adjust viewports, delay, hidden selectors, etc.
34+
1. Run the command `npm run build`.
35+
* This command needs to be run anytime you edit items in `src`
36+
1. Run the command `npm run start`.
37+
* To test a single site, enter its number from the list, or enter `all` to test all sites in `src/sitesToTest.js`
38+
39+
**Troubleshooting**
40+
If you are having issues with the script hanging or BackstopJS taking a long time there may be headless Chrome instances that didn't close properly.
41+
42+
Try `pkill -f "(chrome)?(--headless)"` on Mac/Linux or `Get-CimInstance Win32_Process -Filter "Name = 'chrome.exe' AND CommandLine LIKE '%--headless%'" | %{Stop-Process $_.ProcessId}` in Windows PowerShell.

dist/backstopConfig.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = backstopConfig;
7+
8+
function untrailingSlashIt(string) {
9+
return string.replace(/\/$/, "");
10+
}
11+
12+
function backstopConfig(nonProductionBaseUrl, productionBaseUrl, pathsToTest, siteName) {
13+
const backstopDataDir = `backstop_data/${siteName}`;
14+
const delayTime = 1500;
15+
const acceptableThreshold = 0.1;
16+
const config = {
17+
'id': siteName,
18+
asyncCaptureLimit: 10,
19+
'viewports': [{
20+
'name': 'phone',
21+
'width': 320,
22+
'height': 480
23+
}, {
24+
'name': 'desktop',
25+
'width': 1920,
26+
'height': 1080
27+
}],
28+
'scenarios': [{
29+
'label': 'Homepage',
30+
'url': nonProductionBaseUrl,
31+
'referenceUrl': productionBaseUrl,
32+
'hideSelectors': [],
33+
'selectors': ['document'],
34+
'readyEvent': null,
35+
'delay': delayTime,
36+
'misMatchThreshold': acceptableThreshold
37+
}],
38+
'paths': {
39+
'ci_report': `${backstopDataDir}/ci_report`,
40+
'html_report': `${backstopDataDir}/html_report`,
41+
'bitmaps_reference': `${backstopDataDir}/bitmaps_reference`,
42+
'bitmaps_test': `${backstopDataDir}/bitmaps_test`,
43+
'compare_data': `${backstopDataDir}/bitmaps_test/compare.json`,
44+
'casper_scripts': `${backstopDataDir}/casper_scripts`,
45+
'engine_scripts': `${backstopDataDir}/engine_scripts`
46+
},
47+
'engine': 'puppeteer',
48+
'report': ['browser', 'CI'],
49+
'casperFlags': [],
50+
'debug': false,
51+
'port': 3001
52+
};
53+
const scenarios = pathsToTest.map(function (path) {
54+
return {
55+
'label': path,
56+
'url': untrailingSlashIt(nonProductionBaseUrl) + path,
57+
'referenceUrl': untrailingSlashIt(productionBaseUrl) + path,
58+
'hideSelectors': [],
59+
'selectors': ['document'],
60+
'readyEvent': null,
61+
'delay': delayTime,
62+
'misMatchThreshold': acceptableThreshold
63+
};
64+
});
65+
config.scenarios = config.scenarios.concat(scenarios);
66+
return config;
67+
}

dist/index.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use strict";
2+
3+
var _visualRegressionTests = _interopRequireDefault(require("./visualRegressionTests"));
4+
5+
var _testAllSites = _interopRequireDefault(require("./testAllSites"));
6+
7+
var _minimist = _interopRequireDefault(require("minimist"));
8+
9+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10+
11+
// Local dependencies
12+
// External dependencies
13+
const args = (0, _minimist.default)(process.argv.slice(2), {}); // https://nodejs.org/api/process.html#process_process_platform
14+
// const isWindows = process.platform === "win32";
15+
16+
if (Object.prototype.hasOwnProperty.call(args, 'all')) {
17+
(0, _testAllSites.default)();
18+
} else {
19+
(0, _visualRegressionTests.default)();
20+
}

dist/sitesToTest.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = void 0;
7+
const sitesToTest = {
8+
'return-to-pantheon-test': {
9+
label: 'Return to Pantheon Test',
10+
productionBaseUrl: 'https://live-return-to-pantheon-test.pantheonsite.io/',
11+
nonProductionBaseUrl: 'https://dev-return-to-pantheon-test.pantheonsite.io/',
12+
pathsToTest: ["/2018/04/", "/2018/04/04/hello-world/"]
13+
},
14+
'wordpress-at-scale': {
15+
label: 'WordPress at Scale',
16+
productionBaseUrl: 'https://scalewp.io/',
17+
nonProductionBaseUrl: 'https://dev-wp-microsite.pantheonsite.io/',
18+
pathsToTest: ["/resources", "/elastic-architecture", "/page-caching", "/object-caching", "/query-performance", "/searching-for-scale", "/a-real-world-scalable-architecture", "/development-and-workflow"]
19+
}
20+
};
21+
var _default = sitesToTest;
22+
exports.default = _default;

dist/testAllSites.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = _default;
7+
8+
var _sitesToTest = _interopRequireDefault(require("./sitesToTest"));
9+
10+
var _fancyLog = _interopRequireDefault(require("fancy-log"));
11+
12+
var _ansiColors = _interopRequireDefault(require("ansi-colors"));
13+
14+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15+
16+
// Local dependencies
17+
// External dependencies
18+
const {
19+
execSync
20+
} = require('child_process');
21+
22+
function _default() {
23+
const siteCount = Object.keys(_sitesToTest.default).length;
24+
let i = 1;
25+
26+
for (var site in _sitesToTest.default) {
27+
(0, _fancyLog.default)(_ansiColors.default.yellow(`Testing site ${i} of ${siteCount}...`)); // https://nodejs.org/docs/latest/api/child_process.html#child_process_child_process_execsync_command_options
28+
29+
execSync(`node dist/index.js --site=${site}`, {
30+
stdio: [0, 1, 2]
31+
});
32+
i++;
33+
}
34+
35+
(0, _fancyLog.default)(_ansiColors.default.green(`Done testing ${siteCount} sites!`));
36+
}

dist/throwError.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = throwError;
7+
8+
function throwError(message) {
9+
console.error(message);
10+
process.exit(0);
11+
}

dist/visualRegressionTestSite.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.default = _default;
7+
8+
var _throwError = _interopRequireDefault(require("./throwError"));
9+
10+
var _backstopConfig = _interopRequireDefault(require("./backstopConfig"));
11+
12+
var _sitesToTest = _interopRequireDefault(require("./sitesToTest"));
13+
14+
var _path = _interopRequireDefault(require("path"));
15+
16+
var _backstopjs = _interopRequireDefault(require("backstopjs"));
17+
18+
var _fancyLog = _interopRequireDefault(require("fancy-log"));
19+
20+
var _ansiColors = _interopRequireDefault(require("ansi-colors"));
21+
22+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23+
24+
// Local dependencies
25+
// External dependencies
26+
function _default(siteToTest) {
27+
const siteExists = Object.prototype.hasOwnProperty.call(_sitesToTest.default, siteToTest);
28+
29+
let rootDir = _path.default.dirname(require.main.filename);
30+
31+
if (rootDir.endsWith('dist')) {
32+
rootDir = rootDir.substring(0, rootDir.indexOf('/dist'));
33+
}
34+
35+
if (!siteExists) {
36+
(0, _throwError.default)(_ansiColors.default.red(`${_ansiColors.default.gray(siteToTest)} is not a valid site. Check the name you entered against the ${_ansiColors.default.gray('sitesToTest.js')} config file`));
37+
}
38+
39+
_sitesToTest.default[siteToTest].name = siteToTest;
40+
const site = _sitesToTest.default[siteToTest];
41+
(0, _fancyLog.default)(_ansiColors.default.yellow(`Testing: ${site.label}`));
42+
(0, _fancyLog.default)(`Generating configuration for ${site.label}`);
43+
const currentConfig = (0, _backstopConfig.default)(site.nonProductionBaseUrl, site.productionBaseUrl, site.pathsToTest, site.name);
44+
(0, _fancyLog.default)(`Running Backstop tests for ${site.label}`);
45+
(0, _backstopjs.default)('reference', {
46+
config: currentConfig
47+
}).then(() => {
48+
(0, _fancyLog.default)(`Backstop JS reference complete for ${site.label}! Starting tests.`);
49+
(0, _backstopjs.default)('test', {
50+
config: currentConfig
51+
}).then(() => {
52+
(0, _fancyLog.default)(_ansiColors.default.yellow(`Report saved to ${_ansiColors.default.gray(`${rootDir}/backstop_data/${site.name}/html_report/index.html`)}`));
53+
(0, _fancyLog.default)(_ansiColors.default.green(`Backstop JS tests passed for ${site.label}!`));
54+
}).catch(() => {
55+
(0, _fancyLog.default)(`Report saved to "${rootDir}/backstop_data/${site.name}/html_report/index.html"`);
56+
(0, _throwError.default)(_ansiColors.default.red(`Backstop JS tests failed for ${_ansiColors.default.gray(site.label)}!`));
57+
});
58+
}).catch(() => {
59+
(0, _throwError.default)(_ansiColors.default.red(`Backstop JS reference failed for ${_ansiColors.default.gray(site.label)}! Please check the BackstopReferenceBaseUrl`));
60+
});
61+
}

0 commit comments

Comments
 (0)