Skip to content

Instantly share code, notes, and snippets.

@martinschierle
Last active June 30, 2020 22:21
Show Gist options
  • Save martinschierle/11873c242cf1973d0f7c6e1fae2eec48 to your computer and use it in GitHub Desktop.
Save martinschierle/11873c242cf1973d0f7c6e1fae2eec48 to your computer and use it in GitHub Desktop.
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
var fs = require('fs');
const mergeImg = require('merge-img');
const ampToolboxCacheUrl = require('amp-toolbox-cache-url').createCacheUrl;
const Good3G = {
'offline': false,
'downloadThroughput': 1.5 * 1024 * 1024 / 8,
'uploadThroughput': 750 * 1024 / 8,
'latency': 40
};
const phone = devices['Nexus 5X'];
function injectJs() {
window.lcp = {value: -1, size: -1};
window.cls = {value: 0};
window.lcp_elem = null;
window.cls_elems = [];
var cssPath = function (el) {
var path = [];
while (
(el.nodeName.toLowerCase() != 'html') &&
(el = el.parentNode) &&
path.unshift(el.nodeName.toLowerCase() +
(el.id ? '#' + el.id : '') +
(el.className ? '.' + el.className.replace(/\s+/g, ".") : ''))
);
return path.join(" > ");
}
// Create a PerformanceObserver that calls `updateLCP` for each entry.
const po = new PerformanceObserver((entryList) => {
entryList.getEntries().forEach(function(entry) {
console.log(entry);
if(entry.size > window.lcp.size) {
let e = entry.element;
if(!(e instanceof HTMLElement)) e = e.parentElement;
window.lcp.size = entry.size;
window.lcp.value = entry.startTime;
window.lcp_elem = e;
window.lcp.tagName = e.tagName;
window.lcp.classes = e.getAttribute("class");
window.lcp.path = cssPath(e);
}
});
});
// Observe entries of type `largest-contentful-paint`, including buffered entries,
// i.e. entries that occurred before calling `observe()` below.
po.observe({
type: 'largest-contentful-paint',
buffered: true,
});
try {
const cls_po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
window.cls.value += entry.value;
if(entry.sources && entry.sources.length>0) {
// find the source of maximum size
for(var i = 0;i < entry.sources.length; i++) {
let source = entry.sources[i];
let e = source.node;
if(!e) continue;
if(!(e instanceof HTMLElement)) e = e.parentElement;
window.cls_elems.push(e);
}
}
}
});
cls_po.observe({type: 'layout-shift', buffered: true});
} catch (e) {
console.log(e.message);
// Do nothing if the browser doesn't support this API.
}
}
function highlight() {
function highlightElem(elem, color, width) {
if(!elem) return;
elem.style.border = width + "px solid " + color;
elem.style["box-sizing"] = "border-box";
}
for(var i = 0; i < window.cls_elems.length; i++) {
let elem = window.cls_elems[i];
if(!elem) continue;
highlightElem(elem, "green", 3);
}
highlightElem(window.lcp_elem, "red", 5);
}
async function doBatch(filename) {
/*const browser = await puppeteer.launch({
args: ['--no-sandbox'],
timeout: 10000
});*/
const browser = await puppeteer.launch({
headless: true,
executablePath: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
args: ['--no-sandbox', '--disable-notifications'],
timeout: 10000
});
const page = await browser.newPage();
//await page.evaluateOnNewDocument(injectJs);
//page.on('console', msg => {if(msg.text().indexOf("AMP validation successful") >= 0) validAMP = true;});
await page.emulate(phone);
const client = await page.target().createCDPSession();
await client.send('Network.enable');
await client.send('ServiceWorker.enable');
//await client.send('Network.emulateNetworkConditions', Good3G);
//await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
let urls = fs.readFileSync('input.csv').toString().split("\n");
for(var i = 0; i < Math.min(urls.length, 1000); i++) {
const url = urls[i];
console.log("Processing: " + url);
try {
// inject a function with the code from https://web.dev/cls/#measure-cls-in-javascript
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000});
//page.on('console', consoleObj => console.log(consoleObj.text()));
await page.waitFor(2000); // let's give it a bit more time, to be sure everything's loaded
console.log("Injecting JS...");
await Promise.race([
page.evaluate(injectJs),
page.waitFor(5000)
]);
console.log("Highlighting element...");
await Promise.race([
page.evaluate(highlight),
page.waitFor(5000)
]);
console.log("Gathering results...");
let results = await Promise.race([
page.evaluate(function() {return {'cls': window.cls, 'lcp': window.lcp, 'lcp_elem': window.lcp_elem}}),
page.waitFor(5000)
]);
if(!results) {
console.log("Couldn't retrieve results.");
continue;
}
let cls = results.cls;
let lcp = results.lcp;
let lcp_elem = results.lcp_elem;
console.log("Getting screenshot...");
let screenshot_path = "images/" + new URL(url).hostname + ".jpeg";
try {
await page.screenshot({path: screenshot_path, type: "jpeg", "quality": 30});
} catch(e) {console.log("Can't take screenshot: " + e.message)}
out = new URL(url).hostname + "," + url + "," + lcp.value + ", " + cls.value + "," + lcp.tagName + "," + lcp.classes + "," + lcp.path + "," + screenshot_path;
console.log(out);
fs.appendFile('output.csv', out + "\n", function (err) {});
} catch (error) {
console.log(error);
}
}
}
doBatch('input.csv').then(res => {console.log("Done!");process.exit(0);});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment