Skip to content

Commit c1414bc

Browse files
Merge pull request #5 from uktrade/feature/add-reporting
Add reporting capability
2 parents d0d7970 + 57320ca commit c1414bc

17 files changed

+407
-18
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ dist
44
# test
55
cypress-visual-screenshots/comparison
66
cypress-visual-screenshots/diff
7+
cypress-visual-report
78
cypress/videos
89
cypress/fixtures

cypress/support/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
require('./commands')
2+
3+
after(() => {
4+
cy.task('generateReport')
5+
})

docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ services:
77
dockerfile: Dockerfile
88
volumes:
99
- ./cypress-visual-screenshots:/code/cypress-visual-screenshots
10+
- ./cypress-visual-report:/code/cypress-visual-report

package-lock.json

+6-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"lint": "eslint src/ cypress/",
88
"test:unit": "jest src/",
99
"test:e2e": "npm run build && cypress run --browser chrome --headless",
10-
"build": "npx babel src -d dist"
10+
"build": "npx babel src -d dist && cp src/reporter/template.hbs dist/reporter/"
1111
},
1212
"repository": {
1313
"type": "git",
@@ -31,6 +31,7 @@
3131
"dependencies": {
3232
"cypress": "^5.3.0",
3333
"fs-extra": "^9.0.1",
34+
"handlebars": "^4.7.6",
3435
"pixelmatch": "^5.1.0",
3536
"pngjs": "^3.4.0"
3637
},

src/command.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const compareSnapshotCommand = defaultScreenshotOptions => {
1515
'compareSnapshot',
1616
{ prevSubject: 'optional' },
1717
(subject, name, testThreshold = 0) => {
18-
const testName = `${Cypress.spec.name.replace('.js', '')} - ${name}`
18+
const specName = Cypress.spec.name
19+
const testName = `${specName.replace('.js', '')}-${name}`
1920

2021
// Take a screenshot and copy to baseline if it does not exist
2122
const objToOperateOn = subject ? cy.get(subject) : cy

src/config.js

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const parentDir = path.join(process.cwd(), parentDirFolderName)
55
const baseline = path.join(process.cwd(), parentDirFolderName, 'baseline')
66
const comparison = path.join(process.cwd(), parentDirFolderName, 'comparison')
77
const diff = path.join(process.cwd(), parentDirFolderName, 'diff')
8+
const reportDir = path.join(process.cwd(), 'cypress-visual-report')
89

910
const paths = {
1011
image: {
@@ -18,6 +19,8 @@ const paths = {
1819
diff,
1920
},
2021
parentDir,
22+
reportDir,
23+
report: instance => { return path.join(reportDir, `cypress-visual-report${instance}.html`) },
2124
}
2225

2326
export default paths

src/plugin.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,24 @@ import { createDir,
99
setFilePermission,
1010
renameAndMoveFile } from './utils'
1111
import paths from './config'
12+
import TestStatus from './reporter/test-status'
13+
import { createReport } from './reporter'
14+
15+
const testStatuses = []
1216

1317
const setupFolders = () => {
1418
createDir([paths.dir.baseline, paths.dir.comparison, paths.dir.diff])
1519
}
1620

17-
const tearDownImages = () => {
18-
cleanDir([paths.dir.comparison, paths.dir.diff])
21+
const tearDownDirs = () => {
22+
cleanDir([paths.dir.comparison, paths.dir.diff, paths.reportDir])
23+
}
24+
25+
const generateReport = (instance = '') => {
26+
if (testStatuses.length > 0) {
27+
createReport({ tests: testStatuses, instance })
28+
}
29+
return true
1930
}
2031

2132
const copyScreenshot = args => {
@@ -57,20 +68,24 @@ async function compareSnapshotsPlugin(args) {
5768
)
5869

5970
const percentage = (pixelMismatchResult / diff.width / diff.height) ** 0.5
71+
const testFailed = percentage > args.testThreshold
6072

61-
if (percentage > args.testThreshold) {
73+
if (testFailed) {
6274
diff.pack().pipe(fs.createWriteStream(paths.image.diff(args.testName)))
6375
}
6476

77+
// Saving test status object to build report if task is triggered
78+
testStatuses.push(new TestStatus(!testFailed, args.testName))
79+
6580
return percentage
6681
}
6782

6883
const getCompareSnapshotsPlugin = on => {
6984
// Create folder structure
7085
setupFolders()
7186

72-
// Delete comparison and diff images to ensure a clean run
73-
tearDownImages()
87+
// Delete comparison, diff images and generated reports to ensure a clean run
88+
tearDownDirs()
7489

7590
// Force screenshot resolution to keep consistency of test runs across machines
7691
on('before:browser:launch', (browser, launchOptions) => {
@@ -94,6 +109,7 @@ const getCompareSnapshotsPlugin = on => {
94109
on('task', {
95110
compareSnapshotsPlugin,
96111
copyScreenshot,
112+
generateReport,
97113
})
98114
}
99115

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Reporter create report should create html template 1`] = `
4+
"<!DOCTYPE html>
5+
<html lang=\\"en\\">
6+
<head>
7+
<meta charset=\\"utf-8\\">
8+
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
9+
<meta name=\\"description\\" content=\\"[% title %]\\">
10+
<title>Wdio Image Diff Report</title>
11+
<link rel=\\"stylesheet\\" href=\\"https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\\">
12+
<style type=\\"text/css\\">
13+
body {
14+
background-color: #fafafa;
15+
font-weight: 200;
16+
}
17+
h1, .h1, h2, .h2, h3, .h3 {
18+
margin: 10px;
19+
font-weight: 200;
20+
}
21+
h1, .h1 {
22+
font-size: 30px;
23+
}
24+
h2, .h2 {
25+
font-size: 24px;
26+
}
27+
h4, .h4 {
28+
font-size: 25px;
29+
text-align: center;
30+
}
31+
table {
32+
width: 100%;
33+
}
34+
.table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td {
35+
/*border-color: #ECEFF1;*/
36+
}
37+
span.error {
38+
color: #AD2B2B;
39+
}
40+
span.success {
41+
color: #53891E;
42+
}
43+
span.pending {
44+
color: grey;
45+
}
46+
.test-pass, .suite-pass {
47+
background-color: #C8E6C9;
48+
}
49+
.test-fail, .suite-fail {
50+
background-color: #FFCDD2;
51+
}
52+
.test-skipped, .suite-pending {
53+
background-color: #B5C8D1;;
54+
}
55+
.test-unknown, .suite-unknown {
56+
background-color: #5EA3D1;;
57+
}
58+
.log-output {
59+
background-color: rgba(255, 192, 0, 0.36);
60+
margin-left:24px;
61+
}
62+
.screenshot {
63+
margin-left:24px;
64+
}
65+
.screenshot-large {
66+
height: 100%;
67+
width: 100%;
68+
}
69+
.screenshot-column {
70+
float: left;
71+
width: 50%;
72+
padding: 5px;
73+
}
74+
.table-test {
75+
margin: 0px;
76+
}
77+
tr.test-row {
78+
border-top: 2px solid white;
79+
}
80+
/* Clear floats after image containers */
81+
.row::after {
82+
content: \\"\\";
83+
clear: both;
84+
display: table;
85+
}
86+
</style>
87+
</head>
88+
<body>
89+
<div class=\\"container\\">
90+
<div id=\\"report-header\\" class=\\"page-header\\"><h1></h1></div>
91+
<h2>Results</h2>
92+
<table>
93+
<tr>
94+
<td>
95+
<table class=\\"table table-test\\">
96+
<tr>
97+
<td colspan=\\"2\\" class=\\"test-pass\\">
98+
<span class=\\"success\\">&#10004;</span>
99+
Visual Test 1
100+
</td>
101+
</tr>
102+
</table>
103+
</td>
104+
<tr>
105+
<tr>
106+
<td>
107+
<table class=\\"table table-test\\">
108+
<tr>
109+
<td colspan=\\"2\\" class=\\"test-fail\\">
110+
<span class=\\"error\\">&#10006;</span>
111+
Visual Test 2
112+
</td>
113+
</tr>
114+
<tr class=\\"test-row screenshot\\">
115+
<td colspan=\\"2\\">
116+
<div class=\\"row\\">
117+
<div class=\\"screenshot-column\\">
118+
<h4>Baseline</h4>
119+
<a href=\\"../cypress-visual-screenshots/baseline/Visual Test 2.png\\">
120+
<img src=\\"../cypress-visual-screenshots/baseline/Visual Test 2.png\\" alt=\\"Snow\\" style=\\"width:100%\\">
121+
</a>
122+
</div>
123+
<div class=\\"screenshot-column\\">
124+
<h4>Comparison</h4>
125+
<a href=\\"../cypress-visual-screenshots/comparison/Visual Test 2.png\\">
126+
<img src=\\"../cypress-visual-screenshots/comparison/Visual Test 2.png\\" alt=\\"Snow\\" style=\\"width:100%\\">
127+
</a>
128+
</div>
129+
<div class=\\"screenshot-column\\">
130+
<h4>Diff</h4>
131+
<a href=\\"../cypress-visual-screenshots/diff/Visual Test 2.png\\">
132+
<img src=\\"../cypress-visual-screenshots/diff/Visual Test 2.png\\" alt=\\"Snow\\" style=\\"width:100%\\">
133+
</a>
134+
</div>
135+
</div>
136+
</td>
137+
</tr>
138+
</table>
139+
</td>
140+
<tr>
141+
</table>
142+
</div>
143+
</body>
144+
</html>"
145+
`;

src/reporter/index.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Handlebars from 'handlebars'
2+
import fs from 'fs'
3+
import path from 'path'
4+
import paths from '../config'
5+
6+
const handlebarsHelpers = () => {
7+
Handlebars.registerHelper('testStateIcon', status => {
8+
if (status === 'pass') {
9+
return '<span class="success">&#10004;</span>'
10+
}
11+
return '<span class="error">&#10006;</span>'
12+
})
13+
14+
Handlebars.registerHelper('equal', (lvalue, rvalue, options) => {
15+
// eslint-disable-next-line no-undef
16+
if (arguments.length < 3)
17+
throw new Error("Handlebars Helper equal needs 2 parameters")
18+
if(lvalue !== rvalue) {
19+
return options.inverse(this)
20+
}
21+
return options.fn(this)
22+
})
23+
}
24+
25+
export const generateTemplate = options => {
26+
handlebarsHelpers()
27+
const templateFile = fs.readFileSync(path.resolve(__dirname, '../reporter/template.hbs'), 'utf8')
28+
const template = Handlebars.compile(templateFile)
29+
30+
return template(
31+
options,
32+
{
33+
allowProtoPropertiesByDefault: {
34+
testStatus: true,
35+
name: true,
36+
},
37+
}
38+
)
39+
}
40+
41+
export const createReport = options => {
42+
const template = generateTemplate(options)
43+
fs.writeFile(paths.report(options.instance), template, err => {
44+
if (err) throw err
45+
})
46+
}

src/reporter/reporter.test.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { generateTemplate } from '.'
2+
import TestStatus from './test-status'
3+
4+
describe('Reporter', () => {
5+
describe('create report', () => {
6+
it('should create html template', () => {
7+
const testResult1 = new TestStatus(true, 'Visual Test 1')
8+
const testResult2 = new TestStatus(false, 'Visual Test 2')
9+
const result = generateTemplate(
10+
{ tests: [ testResult1, testResult2 ]
11+
})
12+
expect(result).toMatchSnapshot()
13+
})
14+
})
15+
})

0 commit comments

Comments
 (0)