blob: 9fd778a1e822c24a959587809b75f006724a813f [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import fs from 'fs';
import net from 'net';
import path from 'path';
import pixelmatch from 'pixelmatch';
import {PNG} from 'pngjs';
import {Page} from 'puppeteer';
// These constants have been hand selected by comparing the diffs of screenshots
// between Linux on Mac. Unfortunately font-rendering is platform-specific.
// Even though we force the same antialiasing and hinting settings, some minimal
// differences exist.
const DIFF_PER_PIXEL_THRESHOLD = 0.35;
const DIFF_MAX_PIXELS = 50;
// Waits for the Perfetto UI to be quiescent, using a union of heuristics:
// - Check that the progress bar is not animating.
// - Check that the omnibox is not showing a message.
// - Check that no redraws are pending in our RAF scheduler.
// - Check that all the above is satisfied for |minIdleMs| consecutive ms.
export async function waitForPerfettoIdle(page: Page, minIdleMs?: number) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
minIdleMs = minIdleMs || 3000;
const tickMs = 250;
const timeoutMs = 60000;
const minIdleTicks = Math.ceil(minIdleMs / tickMs);
const timeoutTicks = Math.ceil(timeoutMs / tickMs);
let consecutiveIdleTicks = 0;
let reasons: string[] = [];
for (let ticks = 0; ticks < timeoutTicks; ticks++) {
await new Promise((r) => setTimeout(r, tickMs));
const isShowingMsg = !!(await page.$('.omnibox.message-mode'));
const isShowingAnim = !!(await page.$('.progress.progress-anim'));
const hasPendingRedraws = await (
await page.evaluateHandle('raf.hasPendingRedraws')
).jsonValue();
if (isShowingAnim || isShowingMsg || hasPendingRedraws) {
consecutiveIdleTicks = 0;
reasons = [];
if (isShowingAnim) {
reasons.push('showing progress animation');
}
if (isShowingMsg) {
reasons.push('showing omnibox message');
}
if (hasPendingRedraws) {
reasons.push('has pending redraws');
}
continue;
}
if (++consecutiveIdleTicks >= minIdleTicks) {
return;
}
}
throw new Error(
`waitForPerfettoIdle() failed. Did not reach idle after ${timeoutMs} ms. ` +
`Reasons not considered idle: ${reasons.join(', ')}`,
);
}
export function getTestTracePath(fname: string): string {
const fPath = path.join('test', 'data', fname);
if (!fs.existsSync(fPath)) {
throw new Error('Could not locate trace file ' + fPath);
}
return fPath;
}
export async function compareScreenshots(
reportPath: string,
actualFilename: string,
expectedFilename: string,
) {
if (!fs.existsSync(expectedFilename)) {
throw new Error(
`Could not find ${expectedFilename}. Run wih REBASELINE=1.`,
);
}
const actualImg = PNG.sync.read(fs.readFileSync(actualFilename));
const expectedImg = PNG.sync.read(fs.readFileSync(expectedFilename));
const {width, height} = actualImg;
expect(width).toEqual(expectedImg.width);
expect(height).toEqual(expectedImg.height);
const diffPng = new PNG({width, height});
const diff = await pixelmatch(
actualImg.data,
expectedImg.data,
diffPng.data,
width,
height,
{
threshold: DIFF_PER_PIXEL_THRESHOLD,
},
);
if (diff > DIFF_MAX_PIXELS) {
const diffFilename = actualFilename.replace('.png', '-diff.png');
fs.writeFileSync(diffFilename, PNG.sync.write(diffPng));
fs.appendFileSync(
reportPath,
`${path.basename(actualFilename)};${path.basename(diffFilename)}\n`,
);
fail(`Diff test failed on ${diffFilename}, delta: ${diff} pixels`);
}
return diff;
}
// If the user has a trace_processor_shell --httpd instance open, bail out,
// as that will invalidate the test loading different data.
export async function failIfTraceProcessorHttpdIsActive() {
return new Promise<void>((resolve, reject) => {
const client = new net.Socket();
client.connect(9001, '127.0.0.1', () => {
const err =
'trace_processor_shell --httpd detected on port 9001. ' +
'Bailing out as it interferes with the tests. ' +
'Please kill that and run the test again.';
console.error(err);
client.destroy();
reject(err);
});
client.on('error', (e: {code: string}) => {
expect(e.code).toBe('ECONNREFUSED');
resolve();
});
client.end();
});
}