test: fixup flaky visibility test (#43064)

This commit is contained in:
John Kleinschmidt 2024-07-30 09:14:45 -04:00 committed by GitHub
parent d09a2e513c
commit 1a6e651844
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 32 deletions

View file

@ -3,7 +3,7 @@ import { BaseWindow, BrowserWindow, View, WebContentsView, webContents, screen }
import { once } from 'node:events'; import { once } from 'node:events';
import { closeAllWindows } from './lib/window-helpers'; import { closeAllWindows } from './lib/window-helpers';
import { defer, ifdescribe } from './lib/spec-helpers'; import { defer, ifdescribe, waitUntil } from './lib/spec-helpers';
import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers'; import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers';
describe('WebContentsView', () => { describe('WebContentsView', () => {
@ -136,6 +136,11 @@ describe('WebContentsView', () => {
}); });
describe('visibilityState', () => { describe('visibilityState', () => {
async function haveVisibilityState (view: WebContentsView, state: string) {
const docVisState = await view.webContents.executeJavaScript('document.visibilityState');
return docVisState === state;
}
it('is initially hidden', async () => { it('is initially hidden', async () => {
const v = new WebContentsView(); const v = new WebContentsView();
await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>'); await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
@ -172,7 +177,7 @@ describe('WebContentsView', () => {
const v = new WebContentsView(); const v = new WebContentsView();
w.setContentView(v); w.setContentView(v);
await v.webContents.loadURL('about:blank'); await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible'); await expect(waitUntil(async () => await haveVisibilityState(v, 'visible'))).to.eventually.be.fulfilled();
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))'); const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
// We have to wait until the listener above is fully registered before hiding the window. // We have to wait until the listener above is fully registered before hiding the window.
// On Windows, the executeJavaScript and the visibilitychange can happen out of order // On Windows, the executeJavaScript and the visibilitychange can happen out of order
@ -204,7 +209,7 @@ describe('WebContentsView', () => {
const v = new WebContentsView(); const v = new WebContentsView();
w.setContentView(v); w.setContentView(v);
await v.webContents.loadURL('about:blank'); await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible'); await expect(waitUntil(async () => await haveVisibilityState(v, 'visible'))).to.eventually.be.fulfilled();
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))'); const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
// Ensure the listener has been registered. // Ensure the listener has been registered.

View file

@ -9,6 +9,7 @@ import * as url from 'node:url';
import { SuiteFunction, TestFunction } from 'mocha'; import { SuiteFunction, TestFunction } from 'mocha';
import { BrowserWindow } from 'electron/main'; import { BrowserWindow } from 'electron/main';
import { AssertionError } from 'chai'; import { AssertionError } from 'chai';
import { setTimeout } from 'node:timers/promises';
const addOnly = <T>(fn: Function): T => { const addOnly = <T>(fn: Function): T => {
const wrapped = (...args: any[]) => { const wrapped = (...args: any[]) => {
@ -92,49 +93,50 @@ export async function startRemoteControlApp (extraArgs: string[] = [], options?:
} }
export function waitUntil ( export function waitUntil (
callback: () => boolean, callback: () => boolean|Promise<boolean>,
opts: { rate?: number, timeout?: number } = {} opts: { rate?: number, timeout?: number } = {}
) { ) {
const { rate = 10, timeout = 10000 } = opts; const { rate = 10, timeout = 10000 } = opts;
return new Promise<void>((resolve, reject) => { return (async () => {
let intervalId: NodeJS.Timeout | undefined; // eslint-disable-line prefer-const const ac = new AbortController();
let timeoutId: NodeJS.Timeout | undefined; const signal = ac.signal;
let checkCompleted = false;
let timedOut = false;
const cleanup = () => { const check = async () => {
if (intervalId) clearInterval(intervalId);
if (timeoutId) clearTimeout(timeoutId);
};
const check = () => {
let result; let result;
try { try {
result = callback(); result = await callback();
} catch (e) { } catch (e) {
cleanup(); ac.abort();
reject(e); throw e;
return;
} }
if (result === true) { return result;
cleanup();
resolve();
return true;
}
}; };
if (check()) { setTimeout(timeout, { signal })
return; .then(() => {
timedOut = true;
checkCompleted = true;
});
while (checkCompleted === false) {
const checkSatisfied = await check();
if (checkSatisfied === true) {
ac.abort();
checkCompleted = true;
return;
} else {
await setTimeout(rate);
}
} }
intervalId = setInterval(check, rate); if (timedOut) {
throw new Error(`waitUntil timed out after ${timeout}ms`);
timeoutId = setTimeout(() => { }
timeoutId = undefined; })();
cleanup();
reject(new Error(`waitUntil timed out after ${timeout}ms`));
}, timeout);
});
} }
export async function repeatedly<T> ( export async function repeatedly<T> (