mirror of https://github.com/electron/electron
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
import { NativeImage, nativeImage } from 'electron/common';
|
|
import { BrowserWindow } from 'electron/main';
|
|
|
|
import { AssertionError, expect } from 'chai';
|
|
|
|
import path = require('node:path');
|
|
|
|
import { createArtifact } from './lib/artifacts';
|
|
import { ifdescribe } from './lib/spec-helpers';
|
|
import { closeAllWindows } from './lib/window-helpers';
|
|
|
|
const FIXTURE_PATH = path.resolve(
|
|
__dirname,
|
|
'fixtures',
|
|
'api',
|
|
'corner-smoothing'
|
|
);
|
|
|
|
/**
|
|
* Rendered images may "match" but slightly differ due to rendering artifacts
|
|
* like anti-aliasing and vector path resolution, among others. This tolerance
|
|
* is the cutoff for whether two images "match" or not.
|
|
*
|
|
* From testing, matching images were found to have an average global difference
|
|
* up to about 1.3 and mismatched images were found to have a difference of at
|
|
* least about 7.3.
|
|
*
|
|
* See the documentation on `compareImages` for more information.
|
|
*/
|
|
const COMPARISON_TOLERANCE = 2.5;
|
|
|
|
/**
|
|
* Compares the average global difference of two images to determine if they
|
|
* are similar enough to "match" each other.
|
|
*
|
|
* "Average global difference" is the average difference of pixel components
|
|
* (RGB each) across an entire image.
|
|
*
|
|
* The cutoff for matching/not-matching is defined by the `COMPARISON_TOLERANCE`
|
|
* constant.
|
|
*/
|
|
function compareImages (img1: NativeImage, img2: NativeImage): boolean {
|
|
expect(img1.getSize()).to.deep.equal(
|
|
img2.getSize(),
|
|
'Cannot compare images with different sizes'
|
|
);
|
|
|
|
const bitmap1 = img1.toBitmap();
|
|
const bitmap2 = img2.toBitmap();
|
|
|
|
const { width, height } = img1.getSize();
|
|
|
|
let totalDiff = 0;
|
|
for (let x = 0; x < width; x++) {
|
|
for (let y = 0; y < height; y++) {
|
|
const index = (x + y * width) * 4;
|
|
const pixel1 = bitmap1.subarray(index, index + 4);
|
|
const pixel2 = bitmap2.subarray(index, index + 4);
|
|
const diff =
|
|
Math.abs(pixel1[0] - pixel2[0]) +
|
|
Math.abs(pixel1[1] - pixel2[1]) +
|
|
Math.abs(pixel1[2] - pixel2[2]);
|
|
totalDiff += diff;
|
|
}
|
|
}
|
|
|
|
const avgDiff = totalDiff / (width * height);
|
|
return avgDiff <= COMPARISON_TOLERANCE;
|
|
}
|
|
|
|
/**
|
|
* Recipe for tests.
|
|
*
|
|
* The page is rendered, captured as an image, then compared to an expected
|
|
* result image.
|
|
*/
|
|
async function pageCaptureTestRecipe (
|
|
pagePath: string,
|
|
expectedImgPath: string,
|
|
artifactName: string,
|
|
cornerSmoothingAvailable: boolean = true
|
|
): Promise<void> {
|
|
const w = new BrowserWindow({
|
|
show: false,
|
|
width: 800,
|
|
height: 600,
|
|
useContentSize: true,
|
|
webPreferences: {
|
|
enableCornerSmoothingCSS: cornerSmoothingAvailable
|
|
}
|
|
});
|
|
await w.loadFile(pagePath);
|
|
w.show();
|
|
|
|
// Wait for a render frame to prepare the page.
|
|
await w.webContents.executeJavaScript(
|
|
'new Promise((resolve) => { requestAnimationFrame(() => resolve()); })'
|
|
);
|
|
|
|
const actualImg = await w.webContents.capturePage();
|
|
expect(actualImg.isEmpty()).to.be.false('Failed to capture page image');
|
|
|
|
const expectedImg = nativeImage.createFromPath(expectedImgPath);
|
|
expect(expectedImg.isEmpty()).to.be.false(
|
|
'Failed to read expected reference image'
|
|
);
|
|
|
|
const matches = compareImages(actualImg, expectedImg);
|
|
if (!matches) {
|
|
const artifactFileName = `corner-rounding-expected-${artifactName}.png`;
|
|
await createArtifact(artifactFileName, actualImg.toPNG());
|
|
|
|
throw new AssertionError(
|
|
`Actual image did not match expected reference image. Actual: "${artifactFileName}" in artifacts, Expected: "${path.relative(
|
|
path.resolve(__dirname, '..'),
|
|
expectedImgPath
|
|
)}" in source`
|
|
);
|
|
}
|
|
}
|
|
|
|
// FIXME: these tests rely on live rendering results, which are too variable to
|
|
// reproduce outside of CI, primarily due to display scaling.
|
|
ifdescribe(!!process.env.CI)('-electron-corner-smoothing', () => {
|
|
afterEach(async () => {
|
|
await closeAllWindows();
|
|
});
|
|
|
|
describe('shape', () => {
|
|
for (const available of [true, false]) {
|
|
it(`matches the reference with web preference = ${available}`, async () => {
|
|
await pageCaptureTestRecipe(
|
|
path.join(FIXTURE_PATH, 'shape', 'test.html'),
|
|
path.join(FIXTURE_PATH, 'shape', `expected-${available}.png`),
|
|
`shape-${available}`,
|
|
available
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('system-ui keyword', () => {
|
|
const { platform } = process;
|
|
it(`matches the reference for platform = ${platform}`, async () => {
|
|
await pageCaptureTestRecipe(
|
|
path.join(FIXTURE_PATH, 'system-ui-keyword', 'test.html'),
|
|
path.join(
|
|
FIXTURE_PATH,
|
|
'system-ui-keyword',
|
|
`expected-${platform}.png`
|
|
),
|
|
`system-ui-${platform}`
|
|
);
|
|
});
|
|
});
|
|
});
|