123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- 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}`
- );
- });
- });
- });
|