Adds support for screenshots

This commit is contained in:
Thomas Forgione 2023-02-24 17:12:52 +01:00
parent c5ae2b607a
commit 68eeda7aa6
5 changed files with 75 additions and 15 deletions

1
.gitignore vendored
View File

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

View File

@ -287,3 +287,8 @@ Ouput:
] ]
``` ```
It is also possible to make screenshots of each slide but removing the considered element.
```sh
locator --output output-directory example.html
```

View File

@ -4,6 +4,7 @@ const fs = require('fs').promises;
const process = require('process'); const process = require('process');
const puppeteer = require('puppeteer'); const puppeteer = require('puppeteer');
const quality = require('./quality.js'); const quality = require('./quality.js');
const uuid = require('uuid').v4;
// Size of the rendering of the web page // Size of the rendering of the web page
const size = { width: 1280, height: 720 }; const size = { width: 1280, height: 720 };
@ -42,21 +43,36 @@ async function eprintln(data) {
async function main() { async function main() {
if (process.argv[2] === undefined) { let outputDir = null;
console.error('This program expects an argument.'); let filename = process.argv[2];
console.error('USAGE: locator <path-to-HTML-file>');
if (process.argv[2] === "-o" || process.argv[2] === "--output") {
outputDir = process.argv[3];
filename = process.argv[4];
}
try {
await fs.mkdir(outputDir);
} catch (e) {
eprintln("Couldn't create directory " + outputDir + ": " + e);
process.exit(1);
}
if (filename === undefined) {
eprintln('This program expects an argument.');
eprintln('USAGE: locator <path-to-HTML-file>');
process.exit(1); process.exit(1);
} }
// Path to the HTML file to analyse (given as relative path from current directory) // Path to the HTML file to analyse (given as relative path from current directory)
// We need the full path so that puppeteer is able to access it // We need the full path so that puppeteer is able to access it
const path = process.argv[2].startsWith('/') ? process.argv[2] : process.cwd() + '/' + process.argv[2]; const path = filename.startsWith('/') ? filename : process.cwd() + '/' + filename;
// Check that the file exists // Check that the file exists
try { try {
await fs.access(path, fs.constants.F_OK); await fs.access(path, fs.constants.F_OK);
} catch (e) { } catch (e) {
console.error('No such file: ' + path); eprintln('No such file: ' + path);
process.exit(1); process.exit(1);
} }
@ -72,18 +88,25 @@ async function main() {
// If there is no slide, try to run on HTML body // If there is no slide, try to run on HTML body
if (root === null) { if (root === null) {
console.error('Not a marp HTML file.'); eprintln('Not a marp HTML file.');
process.exit(1); process.exit(1);
} }
// Hide slides controls
await page.evaluate(() => {
for (let elt of document.getElementsByClassName('bespoke-marp-osc')) {
elt.style.visibility = "hidden";
}
});
// Take a first screenshot // Take a first screenshot
await page.screenshot({path: __dirname + '/' + 'screenshot1.png'}); await page.screenshot({path: (outputDir === null ? __dirname : outputDir) + '/' + 'screenshot1.png'});
// Edit the page to shrink elements in order to get better bounding boxes // Edit the page to shrink elements in order to get better bounding boxes
let withSpan = await addSpan(root); let withSpan = await addSpan(root);
// Take another screenshot and check the modification we made didn't change the layout of the page // Take another screenshot and check the modification we made didn't change the layout of the page
await page.screenshot({path: __dirname + '/' + 'screenshot2.png'}); await page.screenshot({path: (outputDir === null ? __dirname : outputDir) + '/' + 'screenshot2.png'});
// Compare both screenshots // Compare both screenshots
let file1 = await fs.readFile(__dirname + '/' + 'screenshot1.png'); let file1 = await fs.readFile(__dirname + '/' + 'screenshot1.png');
@ -96,7 +119,7 @@ async function main() {
// Crash if they're different // Crash if they're different
if (psnr > 70) { if (psnr > 70) {
eprintln("\x1b[33mWarning: " + process.argv[2] + " produced slight diff: psnr = " + psnr + '\x1b[0m'); eprintln("\x1b[33mWarning: " + filename + " produced slight diff: psnr = " + psnr + '\x1b[0m');
} else { } else {
await eprintln("\x1b[31mError: age edit changed the layout: psnr = " + psnr + '\x1b[0m'); await eprintln("\x1b[31mError: age edit changed the layout: psnr = " + psnr + '\x1b[0m');
process.exit(1); process.exit(1);
@ -104,13 +127,18 @@ async function main() {
} }
// Analyse the root and output the result // Analyse the root and output the result
let analyse = await analyseElement(root); let analyse = await analyseElement(root, page, outputDir);
if (process.argv[3] === '--flatten') { if (process.argv[3] === '--flatten') {
analyse = flatten(analyse); analyse = flatten(analyse);
} }
console.log(JSON.stringify(analyse, undefined, 4)); let json = JSON.stringify(analyse, undefined, 4);
if (outputDir === null) {
console.log();
} else {
await fs.writeFile(outputDir + '/annotations.json', json);
}
await browser.close(); await browser.close();
@ -143,7 +171,7 @@ async function addSpan(element) {
// Recursive function to analyse an HTML element. // Recursive function to analyse an HTML element.
// The output is written in hierarchy. // The output is written in hierarchy.
async function analyseElement(element) { async function analyseElement(element, page, outputDir = null) {
// Get some information on the element // Get some information on the element
let tagAttr = await element.getProperty('tagName'); let tagAttr = await element.getProperty('tagName');
let tagName = await tagAttr.jsonValue(); let tagName = await tagAttr.jsonValue();
@ -159,6 +187,7 @@ async function analyseElement(element) {
let analyse = {}; let analyse = {};
analyse.tag = tagName; analyse.tag = tagName;
analyse.class = className; analyse.class = className;
analyse.uuid = uuid();
analyse.box = box; analyse.box = box;
box.x /= size.width; box.x /= size.width;
box.width /= size.width; box.width /= size.width;
@ -166,6 +195,21 @@ async function analyseElement(element) {
box.height /= size.height; box.height /= size.height;
analyse.children = []; analyse.children = [];
if (outputDir !== null) {
// Perform a screenshot where the element is hidden
let previousVisibility = await element.evaluate(el => {
let previousVisibility = el.style.visibility;
el.style.visibility = "hidden";
return previousVisibility;
});
await page.screenshot({path: outputDir + '/' + analyse.uuid + '.png'});
await element.evaluate((el, previousVisibility) => {
el.style.visibility = previousVisibility;
}, [ previousVisibility ]);
}
// Extract the text content if it is a span (we made those spans by ourselves in the addSpan function) // Extract the text content if it is a span (we made those spans by ourselves in the addSpan function)
if (tagName === 'SPAN' && textContent !== "") { if (tagName === 'SPAN' && textContent !== "") {
analyse.text = textContent; analyse.text = textContent;
@ -178,7 +222,7 @@ async function analyseElement(element) {
for (let child of children) { for (let child of children) {
// Recursively analyse the children // Recursively analyse the children
analyse.children.push(await analyseElement(child)); analyse.children.push(await analyseElement(child, page, outputDir));
} }
return analyse; return analyse;

11
package-lock.json generated
View File

@ -10,7 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
"puppeteer": "^19.7.1" "puppeteer": "^19.7.1",
"uuid": "^9.0.0"
}, },
"bin": { "bin": {
"locator": "index.js" "locator": "index.js"
@ -742,6 +743,14 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}, },
"node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@ -11,6 +11,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
"puppeteer": "^19.7.1" "puppeteer": "^19.7.1",
"uuid": "^9.0.0"
} }
} }