feat: customize border radius of Views (#42320)
* feat: add View#setBorderRadius test: initial setBorderRadius tests fix: robustly set border radius chore: add PAUSE_CAPTURE_TESTS for easier screencap dev feat: add view border radius support test: view border radius refactor: cleanup view code * maybe delay capture to fix tests? * refactor: retry screen captures in an attempt to fix flakiness * refactor: ScreenCapture constructor no longer async * increase screen capture timeout, feels a little short * refactor: move rounded rect util into chromium_src * skip some capture tests on mas
This commit is contained in:
parent
cbd11bb605
commit
778d3098a0
15 changed files with 261 additions and 90 deletions
|
@ -75,67 +75,72 @@ function areColorsSimilar (
|
|||
return distance <= distanceThreshold;
|
||||
}
|
||||
|
||||
function imageCenter (image: NativeImage): Electron.Point {
|
||||
const size = image.getSize();
|
||||
function displayCenter (display: Electron.Display): Electron.Point {
|
||||
return {
|
||||
x: size.width / 2,
|
||||
y: size.height / 2
|
||||
x: display.size.width / 2,
|
||||
y: display.size.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
/** Resolve when approx. one frame has passed (30FPS) */
|
||||
export async function nextFrameTime (): Promise<void> {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000 / 30);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for creating and inspecting a screen capture.
|
||||
*
|
||||
* Set `PAUSE_CAPTURE_TESTS` env var to briefly pause during screen
|
||||
* capture for easier inspection.
|
||||
*
|
||||
* NOTE: Not yet supported on Linux in CI due to empty sources list.
|
||||
*/
|
||||
export class ScreenCapture {
|
||||
/** Use the async constructor `ScreenCapture.create()` instead. */
|
||||
private constructor (image: NativeImage) {
|
||||
this.image = image;
|
||||
}
|
||||
/** Timeout to wait for expected color to match. */
|
||||
static TIMEOUT = 3000;
|
||||
|
||||
public static async create (): Promise<ScreenCapture> {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
return ScreenCapture._createImpl(display);
|
||||
}
|
||||
|
||||
public static async createForDisplay (
|
||||
display: Electron.Display
|
||||
): Promise<ScreenCapture> {
|
||||
return ScreenCapture._createImpl(display);
|
||||
constructor (display?: Electron.Display) {
|
||||
this.display = display || screen.getPrimaryDisplay();
|
||||
}
|
||||
|
||||
public async expectColorAtCenterMatches (hexColor: string) {
|
||||
return this._expectImpl(imageCenter(this.image), hexColor, true);
|
||||
return this._expectImpl(displayCenter(this.display), hexColor, true);
|
||||
}
|
||||
|
||||
public async expectColorAtCenterDoesNotMatch (hexColor: string) {
|
||||
return this._expectImpl(imageCenter(this.image), hexColor, false);
|
||||
return this._expectImpl(displayCenter(this.display), hexColor, false);
|
||||
}
|
||||
|
||||
public async expectColorAtPointOnDisplayMatches (
|
||||
hexColor: string,
|
||||
findPoint: (displaySize: Electron.Size) => Electron.Point
|
||||
) {
|
||||
return this._expectImpl(findPoint(this.image.getSize()), hexColor, true);
|
||||
return this._expectImpl(findPoint(this.display.size), hexColor, true);
|
||||
}
|
||||
|
||||
private static async _createImpl (display: Electron.Display) {
|
||||
private async captureFrame (): Promise<NativeImage> {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: display.size
|
||||
thumbnailSize: this.display.size
|
||||
});
|
||||
|
||||
const captureSource = sources.find(
|
||||
(source) => source.display_id === display.id.toString()
|
||||
(source) => source.display_id === this.display.id.toString()
|
||||
);
|
||||
if (captureSource === undefined) {
|
||||
const displayIds = sources.map((source) => source.display_id).join(', ');
|
||||
throw new Error(
|
||||
`Unable to find screen capture for display '${display.id}'\n\tAvailable displays: ${displayIds}`
|
||||
`Unable to find screen capture for display '${this.display.id}'\n\tAvailable displays: ${displayIds}`
|
||||
);
|
||||
}
|
||||
|
||||
return new ScreenCapture(captureSource.thumbnail);
|
||||
if (process.env.PAUSE_CAPTURE_TESTS) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
||||
}
|
||||
|
||||
return captureSource.thumbnail;
|
||||
}
|
||||
|
||||
private async _expectImpl (
|
||||
|
@ -143,15 +148,29 @@ export class ScreenCapture {
|
|||
expectedColor: string,
|
||||
matchIsExpected: boolean
|
||||
) {
|
||||
const actualColor = getPixelColor(this.image, point);
|
||||
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
||||
const gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
||||
let frame: Electron.NativeImage;
|
||||
let actualColor: string;
|
||||
let gotExpectedResult: boolean = false;
|
||||
const expiration = Date.now() + ScreenCapture.TIMEOUT;
|
||||
|
||||
// Continuously capture frames until we either see the expected result or
|
||||
// reach a timeout. This helps avoid flaky tests in which a short waiting
|
||||
// period is required for the expected result.
|
||||
do {
|
||||
frame = await this.captureFrame();
|
||||
actualColor = getPixelColor(frame, point);
|
||||
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
||||
gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
||||
if (gotExpectedResult) break;
|
||||
|
||||
await nextFrameTime(); // limit framerate
|
||||
} while (Date.now() < expiration);
|
||||
|
||||
if (!gotExpectedResult) {
|
||||
// Save the image as an artifact for better debugging
|
||||
const artifactName = await createArtifactWithRandomId(
|
||||
(id) => `color-mismatch-${id}.png`,
|
||||
this.image.toPNG()
|
||||
frame.toPNG()
|
||||
);
|
||||
throw new AssertionError(
|
||||
`Expected color at (${point.x}, ${point.y}) to ${
|
||||
|
@ -161,7 +180,7 @@ export class ScreenCapture {
|
|||
}
|
||||
}
|
||||
|
||||
private image: NativeImage;
|
||||
private display: Electron.Display;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,5 +193,5 @@ export class ScreenCapture {
|
|||
* - Win32 x64: virtual screen display is 0x0
|
||||
*/
|
||||
export const hasCapturableScreen = () => {
|
||||
return process.platform === 'darwin';
|
||||
return process.env.CI ? process.platform === 'darwin' : true;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue