Skip to content

Commit

Permalink
🏗 Add dev-mode for amp visual-diff and split code into modules (#36673
Browse files Browse the repository at this point in the history
)
  • Loading branch information
danielrozenberg committed Nov 2, 2021
1 parent 4b43359 commit 7a44c3f
Show file tree
Hide file tree
Showing 28 changed files with 2,473 additions and 560 deletions.
196 changes: 196 additions & 0 deletions build-system/tasks/visual-diff/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
'use strict';

const argv = require('minimist')(process.argv.slice(2));
const fs = require('fs');
const path = require('path');
const puppeteer = require('puppeteer');
const PuppeteerExtraPluginUserPreferences = require('puppeteer-extra-plugin-user-preferences');
const {addExtra} = require('puppeteer-extra');
const {cyan, yellow} = require('kleur/colors');
const {HOST} = require('./consts');
const {log} = require('./log');

// REPEATING TODO(@ampproject/wg-infra): Update this whenever the Percy backend
// starts using a new version of Chrome to render DOM snapshots.
//
// Steps:
// 1. Open a recent Percy build, and click the “ⓘ” icon
// 2. Note the Chrome major version at the bottom
// 3. Look up the full version at https://en.wikipedia.org/wiki/Google_Chrome_version_history
// 4. Open https://omahaproxy.appspot.com in a browser
// 5. Go to "Tools" -> "Version information"
// 6. Paste the full version (add ".0" at the end) in the "Version" field and click "Lookup"
// 7. Copy the value next to "Branch Base Position" and update the line below
const PUPPETEER_CHROMIUM_REVISION = '870763'; // 91.0.4472.0

const VIEWPORT_WIDTH = 1400;
const VIEWPORT_HEIGHT = 100000;

/**
* Launches a Puppeteer controlled browser.
*
* Waits until the browser is up and reachable, and ties its lifecycle to this
* process's lifecycle.
*
* @param {!puppeteer.BrowserFetcher} browserFetcher Puppeteer browser binaries
* manager.
* @return {Promise<!puppeteer.Browser>} a Puppeteer controlled browser.
*/
async function launchBrowser(browserFetcher) {
const browserOptions = {
args: [
'--disable-background-media-suspend',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-extensions',
'--disable-gpu',
'--disable-renderer-backgrounding',
'--no-sandbox',
'--no-startup-window',
],
dumpio: argv.chrome_debug,
headless: !argv.dev,
executablePath: browserFetcher.revisionInfo(PUPPETEER_CHROMIUM_REVISION)
.executablePath,
waitForInitialPage: false,
};

// @ts-ignore type mismatch in puppeteer-extra.
const puppeteerExtra = addExtra(puppeteer);
puppeteerExtra.use(
PuppeteerExtraPluginUserPreferences({
userPrefs: {
devtools: {
preferences: {
currentDockState: '"undocked"',
},
},
},
})
);
return await puppeteerExtra.launch(browserOptions);
}

/**
* Opens a new browser tab, resizes its viewport, and returns a Page handler.
*
* @param {!puppeteer.Browser} browser a Puppeteer controlled browser.
* @param {?puppeteer.Viewport} viewport optional viewport size object with
* numeric fields `width` and `height`.
* @return {Promise<!puppeteer.Page>}
*/
async function newPage(browser, viewport = null) {
log('verbose', 'Creating new tab');

const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage();
page.setDefaultNavigationTimeout(0);
await page.setJavaScriptEnabled(true);
await page.setRequestInterception(true);
page.on('request', (interceptedRequest) => {
const requestUrl = new URL(interceptedRequest.url());
const mockedFilepath = path.join(
path.dirname(__filename),
'network-mocks',
requestUrl.hostname,
encodeURIComponent(
`${requestUrl.pathname.substr(1)}${requestUrl.search}`
).replace(/%2F/g, '/')
);

if (
requestUrl.protocol === 'data:' ||
requestUrl.hostname === HOST ||
requestUrl.hostname.endsWith(`.${HOST}`)
) {
return interceptedRequest.continue();
} else if (fs.existsSync(mockedFilepath)) {
log(
'verbose',
'Mocked network request for',
yellow(requestUrl.href),
'with file',
cyan(mockedFilepath)
);
return interceptedRequest.respond({
status: 200,
body: fs.readFileSync(mockedFilepath),
});
} else {
log(
'verbose',
'Blocked external network request for',
yellow(requestUrl.href)
);
return interceptedRequest.abort('blockedbyclient');
}
});
await resetPage(page, viewport);
return page;
}

/**
* Resets the size of a tab and loads about:blank.
*
* @param {!puppeteer.Page} page a Puppeteer control browser tab/page.
* @param {?puppeteer.Viewport} viewport optional viewport size object with
* numeric fields `width` and `height`.
* @return {Promise<void>}
*/
async function resetPage(page, viewport = null) {
const width = viewport ? viewport.width : VIEWPORT_WIDTH;
const height = viewport ? viewport.height : VIEWPORT_HEIGHT;

log(
'verbose',
'Resetting tab to',
yellow('about:blank'),
'with size',
yellow(`${width}×${height}`)
);

await page.goto('about:blank');
await page.setViewport({width, height});
}

/**
* Loads task-specific dependencies are returns an instance of BrowserFetcher.
*
* @return {Promise<!puppeteer.BrowserFetcher>}
*/
async function loadBrowserFetcher() {
// @ts-ignore Valid method in Puppeteer's nodejs interface.
// https://github.com/puppeteer/puppeteer/blob/main/src/node/Puppeteer.ts
const browserFetcher = puppeteer.createBrowserFetcher();
const chromiumRevisions = await browserFetcher.localRevisions();
if (chromiumRevisions.includes(PUPPETEER_CHROMIUM_REVISION)) {
log(
'info',
'Using Percy-compatible version of Chromium',
cyan(PUPPETEER_CHROMIUM_REVISION)
);
} else {
log(
'info',
'Percy-compatible version of Chromium',
cyan(PUPPETEER_CHROMIUM_REVISION),
'was not found. Downloading...'
);
await browserFetcher.download(
PUPPETEER_CHROMIUM_REVISION,
(/* downloadedBytes, totalBytes */) => {
// TODO(@ampproject/wg-infra): display download progress.
// Logging every call is too verbose.
}
);
}
return browserFetcher;
}

module.exports = {
PUPPETEER_CHROMIUM_REVISION,
launchBrowser,
loadBrowserFetcher,
newPage,
resetPage,
};
15 changes: 15 additions & 0 deletions build-system/tasks/visual-diff/consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const {PORT} = require('../serve');

const HOST = 'localhost';

// Base tests do not run any special code other than loading the page. Use this
// no-op function instead of null for easier type checking.
const BASE_TEST_FUNCTION = async () => {};

module.exports = {
BASE_TEST_FUNCTION,
HOST,
PORT,
};

0 comments on commit 7a44c3f

Please sign in to comment.