-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.ts
174 lines (158 loc) · 6.13 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Takes care of generating the reports
import * as fs from 'fs';
import { processKraken } from './krakenProcess';
import { processPlaywright } from './playwrightProcess';
import { randomUUID } from 'crypto';
import { Command, Option, program } from 'commander';
import { Report, ScenarioReportFormat, ScenarioStep } from './types';
import { deleteCreateDir } from '../util';
const compareImages = require("resemblejs/compareImages");
import { ComparisonOptions } from 'resemblejs';
import { render } from './render';
async function main(command: Command) {
let opts = command.opts();
let reportDir: string
let imagesDir: string
let reportFile: string
let report: Report
// Fisrt thing is processing
let toprocess = opts?.process;
if (toprocess == 'kraken') {
// Process kraken
reportDir = `screenshots/kraken/report_${opts.prev}_${opts.post}`;
imagesDir = reportDir + '/images';
reportFile = reportDir + '/report.json';
if (!opts.onlyrender) {
report = processKraken(opts.prev, opts.post, reportDir);
} else {
// Only rendering, load the existing one
if (!fs.existsSync(reportFile)) {
throw new Error('No report file found: ' + reportFile);
}
report = JSON.parse(fs.readFileSync(reportFile).toString()) as Report;
}
} else if (toprocess = 'playwright') {
// Process playwright
reportDir = `screenshots/playwright/report_${opts.prev}_${opts.post}`;
reportFile = reportDir + '/report.json';
imagesDir = reportDir + 'images';
deleteCreateDir(reportDir);
deleteCreateDir(imagesDir);
if (!opts.onlyrender) {
report = processPlaywright(opts.prev, opts.post, reportDir);
} else {
// Only rendering, load the existing one
if (!fs.existsSync(reportFile)) {
throw new Error('No report file found: ' + reportFile);
}
report = JSON.parse(fs.readFileSync(reportFile).toString()) as Report;
}
} else {
throw new Error('No process specified');
}
// Then when everything is processed we can do image comparison
if (!opts.onlyrender && report) {
// Make sure we are comparing the same thing
if (report.prev.scenarios.length != report.post.scenarios.length) {
throw new Error('Scenarios count does not match');
}
// Geneerate the diff item in the report
report.diff = { version: 'combined', scenarios: [] };
// Create the images directory, remove an old one
deleteCreateDir(imagesDir);
console.log('Starting diff report, image comparison will take some time...');
// Now by looping through the scenarios for each of the ghost verison, we
// prodcuce a third "test suite" named diff, which is a comparison of
// images of prev and post steps
for (let i = 0; i < report.prev.scenarios.length; i++) {
let prev_scenario = report.prev.scenarios[i]!;
let post_scenario = report.post.scenarios[i]!;
// Generate the steps
let scenario_diff = {
name: prev_scenario.name,
file: prev_scenario.file,
steps: await diffScenariosSteps(prev_scenario, post_scenario, imagesDir)
}
report.diff.scenarios.push(scenario_diff);
}
}
// Finally we can write the report which will be used for the HTML
console.log(`${toprocess} report generated for versions ${opts.prev} and ${opts.post} at ${reportFile.replace(process.cwd(), '')}`);
fs.writeFileSync(reportFile, JSON.stringify(report, null, 2));
// Render the report
let should_exit = render(report, toprocess, reportDir, opts.live);
console.log('Report generated in: ' + reportDir);
if (should_exit) {
process.exit(0);
}
// Wait forever
}
// Pass two scenarios (the same scenario in two ghost versions) and return the
// steps of the third "diff" scenario by running the images over ressemblejs
async function diffScenariosSteps(prev: ScenarioReportFormat, post: ScenarioReportFormat, imagesDir: string): Promise<ScenarioStep[]> {
let steps: ScenarioStep[] = []
if (prev.steps.length != post.steps.length) {
throw new Error('Steps count does not match');
}
for (let i = 0; i < prev.steps.length; i++) {
let prev_step = prev.steps[i]!;
let post_step = post.steps[i]!;
// Generate the diff
let data = await getDiff(prev_step.image, post_step.image, imagesDir + '/' + randomUUID() + '.png');
let image = data.image
data.image = undefined;
// Move the prev and post images to the images dir and change it's path
steps.push({
name: prev_step.name,
image: image,
data: data
});
}
return steps;
}
// Run resemblejso over two paths (scenario step images) and return the data from ressemble
async function getDiff(prev: string, post: string, output: string): Promise<Record<string, any>> {
const options: ComparisonOptions = {
output: {
errorColor: {
red: 255,
green: 0,
blue: 255
},
errorType: "movement",
transparency: 0.3,
largeImageThreshold: 1200,
useCrossOrigin: false,
},
scaleToSameSize: true,
ignore: "antialiasing"
};
// Call resemblejs get the diff and save it at the expected location
const data = await compareImages(fs.readFileSync(prev), fs.readFileSync(post), options)!;
if (data && data.getBuffer) {
let buffer = data.getBuffer(false)
fs.writeFileSync(output, buffer);
return {
image: output,
prev: prev,
post: post,
isSameDimensions: data.isSameDimensions,
dimensionDifference: data.dimensionDifference,
rawMisMatchPercentage: data.rawMisMatchPercentage,
misMatchPercentage: data.misMatchPercentage,
diffBounds: data.diffBounds,
analysisTime: data.analysisTime
};
} else {
throw new Error('No data from resemblejs, or no buffer');
}
}
// Entrypoint
program
.addOption(new Option('--process <tool>', 'Process the report data from plawyright or kraken').choices(['kraken', 'playwright']))
.option('--report', 'Generate the report')
.option('--onlyrender', 'Render the report only')
.option('--live', 'Live render the report')
.requiredOption('--prev <string>', 'Version to take as prev')
.requiredOption('--post <string>', 'Version to taskes as post')
main(program.parse(process.argv));