2023-02-17 06:47:17 +01:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2023-02-17 06:39:02 +01:00
|
|
|
const fs = require('fs').promises;
|
|
|
|
const process = require('process');
|
|
|
|
const puppeteer = require('puppeteer');
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
2023-02-17 06:50:27 +01:00
|
|
|
if (process.argv[2] === undefined) {
|
|
|
|
console.error("This program expects an argument.");
|
|
|
|
console.error("USAGE: locator <path-to-HTML-file>");
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2023-02-17 06:39:02 +01:00
|
|
|
// 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
|
|
|
|
const path = process.argv[2].startsWith('/') ? process.argv[2] : process.cwd() + '/' + process.argv[2];
|
|
|
|
|
|
|
|
// Check that the file exists
|
|
|
|
try {
|
|
|
|
await fs.access(path, fs.constants.F_OK);
|
|
|
|
} catch (e) {
|
|
|
|
console.error("No such file: " + path);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size of the rendering of the web page
|
|
|
|
const size = { width: 1280, height: 720 };
|
|
|
|
|
|
|
|
// Initialize browser
|
|
|
|
const browser = await puppeteer.launch();
|
|
|
|
const page = await browser.newPage();
|
|
|
|
await page.setViewport(size);
|
|
|
|
await page.goto("file://" + path);
|
|
|
|
|
|
|
|
// This will contain all the collected information
|
|
|
|
let info = [];
|
|
|
|
|
2023-02-17 07:59:28 +01:00
|
|
|
// Take a first screenshot
|
|
|
|
await page.screenshot({path: __dirname + '/' + 'screenshot1.png'});
|
|
|
|
|
|
|
|
// Edit the page to shrink elements in order to get better bounding boxes
|
2023-02-17 06:39:02 +01:00
|
|
|
for (let selector of ["h1", "h2", "h3", "h4", "h5", "h6", "a", "img", "p", "ul", "ol", "li"]) {
|
|
|
|
|
|
|
|
let query = selector;
|
2023-02-17 07:59:28 +01:00
|
|
|
|
|
|
|
let shouldCreateSpan = query !== "ul" && query !== "ol" && query != "img";
|
|
|
|
|
|
|
|
if (shouldCreateSpan) {
|
|
|
|
|
|
|
|
await page.evaluate(([query]) => {
|
|
|
|
for (let e of document.querySelectorAll(query)) {
|
|
|
|
if (e.children.length === 0) {
|
|
|
|
e.innerHTML = '<span>' + e.innerHTML + '</span>';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}, [query]);
|
2023-02-17 06:39:02 +01:00
|
|
|
}
|
2023-02-17 07:59:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Take another screenshot and check the modification we made didn't change the layout of the page
|
|
|
|
await page.screenshot({path: __dirname + '/' + 'screenshot2.png'});
|
|
|
|
|
|
|
|
// Compare both screenshots
|
|
|
|
let file1 = await fs.readFile(__dirname + '/' + 'screenshot1.png');
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let selector of ["h1", "h2", "h3", "h4", "h5", "h6", "a", "img", "p", "ul", "ol", "li"]) {
|
|
|
|
|
|
|
|
let query = selector;
|
|
|
|
|
|
|
|
// Shrink the elements horizontally (to be able to get better bounding boxes)
|
|
|
|
let shouldCreateSpan = query !== "ul" && query !== "ol" && query != "img";
|
2023-02-17 06:39:02 +01:00
|
|
|
|
|
|
|
// Query the considered element
|
2023-02-17 11:25:36 +01:00
|
|
|
let parents = await page.$$(query);
|
2023-02-17 07:59:28 +01:00
|
|
|
let elements = await page.$$(query + (shouldCreateSpan ? ' > *:first-child' : ''));
|
2023-02-17 06:39:02 +01:00
|
|
|
|
2023-02-17 11:25:36 +01:00
|
|
|
for (let index = 0; index < elements.length; index ++) {
|
|
|
|
|
|
|
|
let parent = parents[index];
|
|
|
|
let element = elements[index];
|
2023-02-17 07:59:28 +01:00
|
|
|
|
2023-02-17 06:39:02 +01:00
|
|
|
let box = await element.boundingBox();
|
2023-02-17 11:25:36 +01:00
|
|
|
|
|
|
|
let classElement = shouldCreateSpan ? parent : element;
|
|
|
|
let classNameAttr = await classElement.getProperty('className');
|
2023-02-17 11:19:03 +01:00
|
|
|
let className = await classNameAttr.jsonValue();
|
2023-02-17 06:39:02 +01:00
|
|
|
|
|
|
|
// Scale the bounding box
|
|
|
|
box.x /= size.width;
|
|
|
|
box.width /= size.width;
|
|
|
|
box.y /= size.height;
|
|
|
|
box.height /= size.height;
|
|
|
|
|
|
|
|
// Give the selector as type
|
|
|
|
box.type = selector;
|
|
|
|
|
2023-02-17 11:19:03 +01:00
|
|
|
if (className !== "") {
|
|
|
|
box.class = className;
|
|
|
|
}
|
|
|
|
|
2023-02-17 06:39:02 +01:00
|
|
|
info.push(box);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the info by y and the x (top to bottom, then left to right)
|
|
|
|
info.sort((a, b) => {
|
|
|
|
if (a.y < b.y || (a.y == b.y && a.x < b.x)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.y > b.y || (a.y == b.y && a.x > b.x)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Pretty print the output info
|
|
|
|
console.log(JSON.stringify(info, undefined, 4));
|
|
|
|
|
|
|
|
await browser.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|