Skip to content

Instantly share code, notes, and snippets.

@martinschierle
Created August 21, 2020 09:56
Show Gist options
  • Save martinschierle/1d412cf1b8bc76e726e8c9ceddca1071 to your computer and use it in GitHub Desktop.
Save martinschierle/1d412cf1b8bc76e726e8c9ceddca1071 to your computer and use it in GitHub Desktop.
const puppeteer = require('puppeteer');
const { createCanvas, loadImage } = require('canvas')
var fs = require('fs');
const fsExtra = require('fs-extra')
const mergeImg = require('merge-img');
const mustache = require('mustache')
let MAX_URLS = 50;
let TEMPLATE = fs.readFileSync('template.html', 'utf8');
const Good3G = {
'offline': false,
'downloadThroughput': 1.5 * 1024 * 1024 / 8,
'uploadThroughput': 750 * 1024 / 8,
'latency': 40
};
const phone = puppeteer.devices['Nexus 5X'];
function injectJs() {
window.cls = 0;
let po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
window.cls += entry.value;
}
});
po.observe({type: 'layout-shift', buffered: true});
}
async function getBrowser(allowWebFonts) {
let args = ['--no-sandbox'];
if(!allowWebFonts) {
args.push("--disable-remote-fonts");
}
const browser = await puppeteer.launch({
args: args,
//headless: false,
//executablePath: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
timeout: 10000
});
return browser;
}
async function getNewPage(browser) {
const page = await browser.newPage();
await page.emulate(phone);
await page.setCacheEnabled(false); // no cache, so that we can reuse the same page several times
return page;
}
async function getCLS(page, url) {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000});
await page.waitFor(1000); // 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)
]);
page.waitFor(2000);
console.log("Gathering data...");
let cls = await Promise.race([
page.evaluate(function() {return window.cls}),
page.waitFor(5000)
]);
return cls;
}
async function doBatch(urls, max) {
// reset output file and images dir
fsExtra.emptyDirSync("output");
fs.mkdirSync("output/images");
let results = [];
for(var k = 0; k < Math.min(max, urls.length); k++) {
const url = urls[k];
console.log("Processing: " + url);
try {
let withFontBrowser = await getBrowser(true);
let withFontPage = await getNewPage(withFontBrowser);
let withFontCLS = (await getCLS(withFontPage, url) + await getCLS(withFontPage, url))/2; // two runs to average things out
await withFontPage.screenshot({path: 'output/images/withfonts.png'});
await withFontBrowser.close();
let noFontsBrowser = await getBrowser(false);
let noFontsPage = await getNewPage(noFontsBrowser);
let noFontCLS = (await getCLS(noFontsPage, url) + await getCLS(noFontsPage, url))/2; // two runs to average things out
await noFontsPage.screenshot({path: 'output/images/nofonts.png'});
await noFontsBrowser.close();
let screenshotname = "images/" + new URL(url).hostname+'.png';
mergeImg(['output/images/withfonts.png', 'output/images/nofonts.png'])
.then((img) => {
// Save image as file
img.write("output/"+screenshotname, () => console.log('done'));
});
results.push({url: url, withFontCLS: withFontCLS, noFontCLS: noFontCLS, diff:(noFontCLS - withFontCLS), screenshot: screenshotname});
} catch (error) {
console.log(error);
//process.exit(0);
}
}
// write out result html
results.sort((a, b) => (a.diff < b.diff) ? -1 : 1)
var rendered = mustache.render(TEMPLATE, {items: results});
fs.writeFileSync('output/index.html', rendered)
}
let urls = fs.readFileSync('input.csv').toString().split("\n");
doBatch(urls, 200).then(res => {console.log("Done!");process.exit(0);});
<html>
<head>
<style>
#mainTable tr:nth-child(odd){
background-color: lightblue;
}
thead {
font-weight: bold;
background-color: lightgrey;
}
.screenshot {
margin:20px;
width:150px;
}
.heatmap {
width:400px;
}
td {
word-break:break-all;
width: 16%;
}
</style>
</head>
<body onload="renderHello()">
<h1>Analysis results for Font-related layout shifts</h1>
<table>
<thead>
<tr>
<td width="10%">URL</td>
<td>CLS With Web-Fonts</td>
<td>CLS Without Webfonts</td>
<td>CLS Diff</td>
<td>Screenshot</td>
</tr>
</thead>
<tbody id="mainTable">
{{#items}}
<tr>
<td>{{url}}</td>
<td>{{withFontCLS}}</td>
<td>{{noFontCLS}}</td>
<td>{{diff}}</td>
<td><img src="{{screenshot}}" width="400px"></td>
</tr>
{{/items}}
</tbody>
</table>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment