This commit is contained in:
Thomas Forgione 2023-02-20 14:59:52 +01:00
parent 38d5cb9ed0
commit c5ae2b607a
5 changed files with 132 additions and 9 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules
examples
run.sh
screenshot*

View File

@ -3,10 +3,43 @@
const fs = require('fs').promises;
const process = require('process');
const puppeteer = require('puppeteer');
const quality = require('./quality.js');
// Size of the rendering of the web page
const size = { width: 1280, height: 720 };
// Logging helper
function write(stream, chunk, encoding='utf8') {
return new Promise((resolve, reject) => {
const errListener = (err) => {
stream.removeListener('error', errListener);
reject(err);
};
stream.addListener('error', errListener);
const callback = () => {
stream.removeListener('error', errListener);
resolve(undefined);
};
stream.write(chunk, encoding, callback);
});
}
async function print(data) {
await write(process.stdout, data);
}
async function eprint(data) {
await write(process.stderr, data);
}
async function println(data) {
await write(process.stdout, data + '\n');
}
async function eprintln(data) {
await write(process.stderr, data + '\n');
}
async function main() {
if (process.argv[2] === undefined) {
@ -57,9 +90,17 @@ async function main() {
let file2 = await fs.readFile(__dirname + '/' + 'screenshot2.png');
let filesAreSame = file1.map((x, i) => x === file2[i]).reduce((a, b) => a && b, true);
// Crash if they're different
if (!filesAreSame) {
throw new Error("Page edit changed the layout");
// Check psnr
let psnr = await quality.psnr(__dirname + '/' + 'screenshot1.png', __dirname + '/' + 'screenshot2.png');
// Crash if they're different
if (psnr > 70) {
eprintln("\x1b[33mWarning: " + process.argv[2] + " produced slight diff: psnr = " + psnr + '\x1b[0m');
} else {
await eprintln("\x1b[31mError: age edit changed the layout: psnr = " + psnr + '\x1b[0m');
process.exit(1);
}
}
// Analyse the root and output the result
@ -82,14 +123,19 @@ async function addSpan(element) {
for (let elt of elts) {
let value = await elt.evaluate(el => el.textContent, element);
if (value !== "") {
await elt.evaluate(el => {
if (el.innerHTML.indexOf('<li>') === -1) {
let html = await elt.evaluate(el => {
if (el.innerHTML.indexOf('<li>') === -1 && el.innerHTML.indexOf('<p>') === -1) {
let tmp = el.innerHTML;
el.innerHTML = '<span class="sized-span">' + el.innerHTML + '</span>';
return true;
return [tmp, el.innerHTML];
} else {
return false;
return null;
}
});
if (html !== null) {
await eprintln("\x1b[36mReplaced " + JSON.stringify(html[0]) + " by " + JSON.stringify(html[1]) + '\x1b[0m');
}
}
}

15
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"pngjs": "^7.0.0",
"puppeteer": "^19.7.1"
},
"bin": {
@ -48,9 +49,9 @@
}
},
"node_modules/@types/node": {
"version": "18.13.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz",
"integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==",
"version": "18.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz",
"integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==",
"optional": true
},
"node_modules/@types/yauzl": {
@ -540,6 +541,14 @@
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
},
"node_modules/pngjs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
"engines": {
"node": ">=14.19.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",

View File

@ -10,6 +10,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"pngjs": "^7.0.0",
"puppeteer": "^19.7.1"
}
}

65
quality.js Normal file
View File

@ -0,0 +1,65 @@
const { PNG } = require('pngjs');
const fs = require('fs');
// Fixed from https://www.npmjs.com/package/png-quality
async function loadPngFile(pathOrBuffer) {
// Load buffer of path
if (!(pathOrBuffer instanceof Buffer)) {
pathOrBuffer = await new Promise((resolve, reject) => {
fs.readFile(pathOrBuffer, (err, data) => {
if (err) return reject(err)
resolve(data)
});
})
}
// Load PNG from buffer
return await new Promise((resolve, reject) => {
const png = new PNG()
png.parse(pathOrBuffer, err => {
if (err) return reject(err)
resolve(png)
})
})
}
async function mse(png1, png2) {
const pngs = [png1, png2];
for (let i in pngs) {
if (!(pngs[i] instanceof PNG)) pngs[i] = await loadPngFile(pngs[i])
}
if (pngs[0].width !== pngs[1].width || pngs[0].height !== pngs[1].height) {
throw new Error('Width or height does not equal')
}
if (pngs[0].data.length !== pngs[1].data.length) {
throw new Error('Data buffer length does not equal')
}
const square = (a) => a * a,
channelIndex = [0, 1, 2],
channelMax = 255 * 255,
area = pngs[0].width * pngs[1].height
let mse = 0
for (let i = 0; i < pngs[0].data.length; i += 4) {
const rgbas = pngs.map(png => png.data.slice(i, i + 4))
const rgbs = rgbas.map(rgba => channelIndex.map(i => rgba[i] * rgba[3]))
channelIndex.forEach(i => mse += square(rgbs[0][i] - rgbs[1][i]))
}
return mse / 3.0 / (channelMax * channelMax) / area
}
async function psnr(png1, png2) {
const m = await mse(png1, png2)
return 10 * Math.log10(1 / m)
}
module.exports = {
loadPngFile,
mse,
psnr,
};