fix: Prevent mocha tests failures when window does not have focus. (#9332)

* chore: Add puppeteer-core as a dev dependency.

* fix: Make mocha tests run in a fake-focused window.

* fix: Make `test:mocha:interactive` use the same gulp codepath as `test`.
This commit is contained in:
Aaron Dodson
2025-08-27 12:28:06 -07:00
committed by GitHub
parent 3d28ca8448
commit b2bbe965ba
5 changed files with 111 additions and 18 deletions

View File

@@ -45,7 +45,11 @@ import {
publishBeta, publishBeta,
recompile, recompile,
} from './scripts/gulpfiles/release_tasks.mjs'; } from './scripts/gulpfiles/release_tasks.mjs';
import {generators, test} from './scripts/gulpfiles/test_tasks.mjs'; import {
generators,
interactiveMocha,
test,
} from './scripts/gulpfiles/test_tasks.mjs';
const clean = parallel(cleanBuildDir, cleanReleaseDir); const clean = parallel(cleanBuildDir, cleanReleaseDir);
@@ -80,6 +84,7 @@ export {
clean, clean,
test, test,
generators as testGenerators, generators as testGenerators,
interactiveMocha,
buildAdvancedCompilationTest, buildAdvancedCompilationTest,
createRC as gitCreateRC, createRC as gitCreateRC,
docs, docs,

89
package-lock.json generated
View File

@@ -51,6 +51,7 @@
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"puppeteer-core": "^24.17.0",
"readline-sync": "^1.4.10", "readline-sync": "^1.4.10",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
@@ -1282,18 +1283,18 @@
} }
}, },
"node_modules/@puppeteer/browsers": { "node_modules/@puppeteer/browsers": {
"version": "2.10.4", "version": "2.10.7",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.4.tgz", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz",
"integrity": "sha512-9DxbZx+XGMNdjBynIs4BRSz+M3iRDeB7qRcAr6UORFLphCIM2x3DXgOucvADiifcqCE4XePFUKcnaAMyGbrDlQ==", "integrity": "sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "^4.4.1",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"progress": "^2.0.3", "progress": "^2.0.3",
"proxy-agent": "^6.5.0", "proxy-agent": "^6.5.0",
"semver": "^7.7.1", "semver": "^7.7.2",
"tar-fs": "^3.0.8", "tar-fs": "^3.1.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@@ -2960,6 +2961,20 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chromium-bidi": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz",
"integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"mitt": "^3.0.1",
"zod": "^3.24.1"
},
"peerDependencies": {
"devtools-protocol": "*"
}
},
"node_modules/ci-info": { "node_modules/ci-info": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -3612,6 +3627,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/devtools-protocol": {
"version": "0.0.1475386",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz",
"integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/diff": { "node_modules/diff": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
@@ -6895,6 +6917,13 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true,
"license": "MIT"
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "0.5.6", "version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -7925,6 +7954,24 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/puppeteer-core": {
"version": "24.17.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.17.0.tgz",
"integrity": "sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.10.7",
"chromium-bidi": "8.0.0",
"debug": "^4.4.1",
"devtools-protocol": "0.0.1475386",
"typed-query-selector": "^2.12.0",
"ws": "^8.18.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -8993,9 +9040,9 @@
} }
}, },
"node_modules/tar-fs": { "node_modules/tar-fs": {
"version": "3.0.9", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
"integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -9212,6 +9259,13 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/typed-query-selector": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
"integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==",
"dev": true,
"license": "MIT"
},
"node_modules/typedarray": { "node_modules/typedarray": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -9773,9 +9827,10 @@
"dev": true "dev": true
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.18.0", "version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@@ -10029,6 +10084,16 @@
"dependencies": { "dependencies": {
"safe-buffer": "~5.2.0" "safe-buffer": "~5.2.0"
} }
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@@ -45,7 +45,7 @@
"test": "gulp test", "test": "gulp test",
"test:browser": "cd tests/browser && npx mocha", "test:browser": "cd tests/browser && npx mocha",
"test:generators": "gulp testGenerators", "test:generators": "gulp testGenerators",
"test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"http-server ./ -o /tests/mocha/index.html -c-1\"", "test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"gulp interactiveMocha\"",
"test:compile:advanced": "gulp buildAdvancedCompilationTest --debug", "test:compile:advanced": "gulp buildAdvancedCompilationTest --debug",
"updateGithubPages": "npm ci && gulp gitUpdateGithubPages" "updateGithubPages": "npm ci && gulp gitUpdateGithubPages"
}, },
@@ -138,6 +138,7 @@
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"puppeteer-core": "^24.17.0",
"readline-sync": "^1.4.10", "readline-sync": "^1.4.10",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",

View File

@@ -257,9 +257,9 @@ async function metadata() {
* Run Mocha tests inside a browser. * Run Mocha tests inside a browser.
* @return {Promise} Asynchronous result. * @return {Promise} Asynchronous result.
*/ */
async function mocha() { async function mocha(exitOnCompletion = true) {
return runTestTask('mocha', async () => { return runTestTask('mocha', async () => {
const result = await runMochaTestsInBrowser().catch(e => { const result = await runMochaTestsInBrowser(exitOnCompletion).catch(e => {
throw e; throw e;
}); });
if (result) { if (result) {
@@ -269,6 +269,14 @@ async function mocha() {
}); });
} }
/**
* Run Mocha tests inside a browser and keep the browser open upon completion.
* @return {Promise} Asynchronous result.
*/
export async function interactiveMocha() {
return mocha(false);
}
/** /**
* Helper method for comparison file. * Helper method for comparison file.
* @param {string} file1 First target file. * @param {string} file1 First target file.

View File

@@ -15,9 +15,12 @@ const {posixPath} = require('../../scripts/helpers');
* Runs the Mocha tests in this directory in Chrome. It uses webdriverio to * Runs the Mocha tests in this directory in Chrome. It uses webdriverio to
* launch Chrome and load index.html. Outputs a summary of the test results * launch Chrome and load index.html. Outputs a summary of the test results
* to the console. * to the console.
*
* @param {boolean} exitOnCompletetion True if the browser should automatically
* quit after tests have finished running.
* @return {number} 0 on success, 1 on failure. * @return {number} 0 on success, 1 on failure.
*/ */
async function runMochaTestsInBrowser() { async function runMochaTestsInBrowser(exitOnCompletion = true) {
const options = { const options = {
capabilities: { capabilities: {
browserName: 'chrome', browserName: 'chrome',
@@ -45,6 +48,17 @@ async function runMochaTestsInBrowser() {
console.log('Loading URL: ' + url); console.log('Loading URL: ' + url);
await browser.url(url); await browser.url(url);
// Toggle the devtools setting to emulate focus, so that the window will
// always act as if it has focus regardless of the state of the window manager
// or operating system. This improves the reliability of FocusManager-related
// tests.
const puppeteer = await browser.getPuppeteer();
await browser.call(async () => {
const page = (await puppeteer.pages())[0];
const session = await page.createCDPSession();
await session.send('Emulation.setFocusEmulationEnabled', { enabled: true });
});
await browser.waitUntil(async() => { await browser.waitUntil(async() => {
const elem = await browser.$('#failureCount'); const elem = await browser.$('#failureCount');
const text = await elem.getAttribute('tests_failed'); const text = await elem.getAttribute('tests_failed');
@@ -74,7 +88,7 @@ async function runMochaTestsInBrowser() {
if (parseInt(numOfFailure) !== 0) { if (parseInt(numOfFailure) !== 0) {
return 1; return 1;
} }
await browser.deleteSession(); if (exitOnCompletion) await browser.deleteSession();
return 0; return 0;
} }