see if this cleans up the tests

This commit is contained in:
John Kleinschmidt
2024-11-07 17:15:54 -05:00
parent 1c89130fcf
commit ae5a5ebe19
10 changed files with 217 additions and 181 deletions

View File

@@ -225,15 +225,7 @@ jobs:
cd src\electron
echo ELECTRON_TEST_RESULTS_DIR=$PWD\junit >> $env:GITHUB_ENV
# Get which tests are on this shard
if ('${{ matrix.shard }}' -eq '1') {
$tests_files='spec\api-web-contents-spec.ts spec\webview-spec.ts'
} elseif ('${{ matrix.shard }}' -eq '2') {
$tests_files='spec\webview-spec.ts spec\api-ipc-spec.ts'
} elseif ('${{ matrix.shard }}' -eq '3') {
$tests_files='spec\webview-spec.ts spec\api-native-image-spec.ts'
} elseif ('${{ matrix.shard }}' -eq '4') {
$tests_files='spec\api-web-contents-spec.ts spec\api-menu-item-spec.ts'
}
$tests_files=node script\split-tests ${{ matrix.shard }} 4
echo tests_files=$tests_files >> $env:GITHUB_ENV
if ('${{ inputs.target-arch }}' -eq 'x86') {
echo npm_config_arch=ia32 >> $env:GITHUB_ENV

View File

@@ -206,6 +206,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
const events = [
@@ -370,6 +371,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('should emit did-start-loading event', async () => {
@@ -533,6 +535,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('allows the window to be closed from the event listener', async () => {
@@ -651,6 +654,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('allows the window to be closed from the event listener', (done) => {
@@ -839,6 +843,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('is emitted on redirects', async () => {
const willRedirect = once(w.webContents, 'will-redirect');
@@ -935,6 +940,10 @@ describe('BrowserWindow module', () => {
});
url = (await listen(server)).url;
});
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('for initial navigation, event order is consistent', async () => {
const firedEvents: string[] = [];
const expectedEventOrder = [
@@ -3565,6 +3574,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('exposes ipcRenderer to preload script', async () => {
@@ -5284,6 +5294,7 @@ describe('BrowserWindow module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('is true when the main frame is loading', async () => {

View File

@@ -372,6 +372,7 @@ describe('session module', () => {
afterEach(() => {
if (server) {
server.close();
server = null as unknown as http.Server;
}
customSession = null as any;
});
@@ -659,6 +660,7 @@ describe('session module', () => {
afterEach((done) => {
server.close(done);
server = null as unknown as http.Server;
});
afterEach(closeAllWindows);
@@ -748,7 +750,7 @@ describe('session module', () => {
describe('ses.clearAuthCache()', () => {
it('can clear http auth info from cache', async () => {
const ses = session.fromPartition('auth-cache');
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
const credentials = auth(req);
if (!credentials || credentials.name !== 'test' || credentials.pass !== 'test') {
res.statusCode = 401;
@@ -758,6 +760,10 @@ describe('session module', () => {
res.end('authenticated');
}
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
const { port } = await listen(server);
const fetch = (url: string) => new Promise((resolve, reject) => {
const request = net.request({ url, session: ses });
@@ -841,6 +847,13 @@ describe('session module', () => {
};
describe('session.downloadURL', () => {
let server: http.Server;
afterEach(() => {
if (server) {
server.close();
server = null as unknown as http.Server;
}
});
it('can perform a download', (done) => {
session.defaultSession.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
@@ -857,7 +870,7 @@ describe('session module', () => {
});
it('can perform a download with a valid auth header', async () => {
const server = http.createServer((req, res) => {
server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
@@ -919,7 +932,7 @@ describe('session module', () => {
});
it('correctly handles a download with an invalid auth header', async () => {
const server = http.createServer((req, res) => {
server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
@@ -963,6 +976,13 @@ describe('session module', () => {
});
describe('webContents.downloadURL', () => {
let server: http.Server;
afterEach(() => {
if (server) {
server.close();
server = null as unknown as http.Server;
}
});
it('can perform a download', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
@@ -980,7 +1000,7 @@ describe('session module', () => {
});
it('can perform a download with a valid auth header', async () => {
const server = http.createServer((req, res) => {
server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
@@ -1036,7 +1056,7 @@ describe('session module', () => {
});
it('correctly handles a download and an invalid auth header', async () => {
const server = http.createServer((req, res) => {
server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
@@ -1255,11 +1275,15 @@ describe('session module', () => {
it('can be resumed', async () => {
const downloadFilePath = path.join(fixtures, 'logo.png');
const rangeServer = http.createServer((req, res) => {
let rangeServer = http.createServer((req, res) => {
const options = { root: fixtures };
send(req, req.url!, options)
.on('error', (error: any) => { throw error; }).pipe(res);
});
defer(() => {
rangeServer.close();
rangeServer = null as unknown as http.Server;
});
try {
const { url } = await listen(rangeServer);
const w = new BrowserWindow({ show: false });
@@ -1327,6 +1351,7 @@ describe('session module', () => {
});
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('cancels any pending requests when cleared', async () => {

View File

@@ -15,6 +15,7 @@ import { closeAllWindows } from './lib/window-helpers';
describe('shell module', () => {
describe('shell.openExternal()', () => {
let envVars: Record<string, string | undefined> = {};
let server: http.Server;
beforeEach(function () {
envVars = {
@@ -31,8 +32,12 @@ describe('shell module', () => {
process.env.BROWSER = envVars.browser;
process.env.DISPLAY = envVars.display;
}
await closeAllWindows();
if (server) {
server.close();
server = null as unknown as http.Server;
}
});
afterEach(closeAllWindows);
async function urlOpened () {
let url = 'http://127.0.0.1';
@@ -50,7 +55,7 @@ describe('shell module', () => {
const w = new BrowserWindow({ show: true });
requestReceived = once(w, 'blur');
} else {
const server = http.createServer((req, res) => {
server = http.createServer((req, res) => {
res.end();
});
url = (await listen(server)).url;

View File

@@ -349,6 +349,7 @@ describe('webContents module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('works after page load and during subframe load', async () => {
@@ -391,6 +392,14 @@ describe('webContents module', () => {
describe('loadURL() promise API', () => {
let w: BrowserWindow;
let s: http.Server;
afterEach(() => {
if (s) {
s.close();
s = null as unknown as http.Server;
}
});
beforeEach(async () => {
w = new BrowserWindow({ show: false });
@@ -494,19 +503,18 @@ describe('webContents module', () => {
});
it('rejects if the load is aborted', async () => {
const s = http.createServer(() => { /* never complete the request */ });
s = http.createServer(() => { /* never complete the request */ });
const { port } = await listen(s);
const p = expect(w.loadURL(`http://127.0.0.1:${port}`)).to.eventually.be.rejectedWith(Error, /ERR_ABORTED/);
// load a different file before the first load completes, causing the
// first load to be aborted.
await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'));
await p;
s.close();
});
it("doesn't reject when a subframe fails to load", async () => {
let resp = null as unknown as http.ServerResponse;
const s = http.createServer((req, res) => {
s = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('<iframe src="http://err.name.not.resolved"></iframe>');
resp = res;
@@ -524,12 +532,11 @@ describe('webContents module', () => {
await p;
resp.end();
await main;
s.close();
});
it("doesn't resolve when a subframe loads", async () => {
let resp = null as unknown as http.ServerResponse;
const s = http.createServer((req, res) => {
s = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('<iframe src="about:blank"></iframe>');
resp = res;
@@ -548,7 +555,6 @@ describe('webContents module', () => {
resp.destroy(); // cause the main request to fail
await expect(main).to.eventually.be.rejected()
.and.have.property('errno', -355); // ERR_INCOMPLETE_CHUNKED_ENCODING
s.close();
});
it('subsequent load failures reject each time', async () => {
@@ -1521,11 +1527,15 @@ describe('webContents module', () => {
it('can persist when it contains iframe', (done) => {
const w = new BrowserWindow({ show: false });
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
setTimeout(200).then(() => {
res.end();
});
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
listen(server).then(({ url }) => {
const content = `<iframe src=${url}></iframe>`;
w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
@@ -1538,8 +1548,6 @@ describe('webContents module', () => {
done();
} catch (e) {
done(e);
} finally {
server.close();
}
}
});
@@ -1586,6 +1594,7 @@ describe('webContents module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
it('cannot persist zoom level after navigation with webFrame', async () => {
@@ -1745,6 +1754,7 @@ describe('webContents module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
afterEach(closeAllWindows);
@@ -1907,6 +1917,7 @@ describe('webContents module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
});
const events = [
@@ -2016,19 +2027,21 @@ describe('webContents module', () => {
afterEach(closeAllWindows);
it('propagates referrer information to new target=_blank windows', (done) => {
const w = new BrowserWindow({ show: false });
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
if (req.url === '/should_have_referrer') {
try {
expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
return done();
} catch (e) {
return done(e);
} finally {
server.close();
}
}
res.end('<a id="a" href="/should_have_referrer" target="_blank">link</a>');
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
listen(server).then(({ url }) => {
w.webContents.once('did-finish-load', () => {
w.webContents.setWindowOpenHandler(details => {
@@ -2044,7 +2057,7 @@ describe('webContents module', () => {
it('propagates referrer information to windows opened with window.open', (done) => {
const w = new BrowserWindow({ show: false });
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
if (req.url === '/should_have_referrer') {
try {
expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
@@ -2055,6 +2068,10 @@ describe('webContents module', () => {
}
res.end('');
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
listen(server).then(({ url }) => {
w.webContents.once('did-finish-load', () => {
w.webContents.setWindowOpenHandler(details => {
@@ -2636,7 +2653,9 @@ describe('webContents module', () => {
after(() => {
server.close();
server = null as unknown as http.Server;
proxyServer.close();
proxyServer = null as unknown as http.Server;
});
it('is emitted when navigating', async () => {

View File

@@ -464,6 +464,9 @@ describe('webFrameMain module', () => {
it('is not emitted upon cross-origin navigation', async () => {
const server = await createServer();
defer(() => {
server.server.close();
});
const w = new BrowserWindow({ show: false });
await w.webContents.loadURL(server.url);

View File

@@ -2540,6 +2540,7 @@ describe('chromium features', () => {
describe('websockets', () => {
it('has user agent', async () => {
const server = http.createServer();
defer(() => server.close());
const { port } = await listen(server);
const wss = new ws.Server({ server });
const finished = new Promise<string | undefined>((resolve, reject) => {
@@ -3692,6 +3693,7 @@ describe('navigator.usb', () => {
res.setHeader('Content-Type', 'text/html');
res.end('<body>');
});
defer(() => server.close());
serverUrl = (await listen(server)).url;

View File

@@ -1,117 +1,7 @@
/* eslint-disable */
const { app, protocol } = require('electron');
const { createHook } = require('node:async_hooks');
const childProcess = require('node:child_process');
const { readFileSync } = require('node:fs');
const fs = require('node:fs');
const { relative } = require('node:path');
const path = require('node:path');
const { fileURLToPath } = require('node:url');
/* eslint-enable */
const IGNORED_TYPES = [
'TIMERWRAP',
'PROMISE',
'PerformanceObserver',
'RANDOMBYTESREQUEST'
];
const asyncResources = new Map();
const hook = createHook({
init (asyncId, type, triggerAsyncId, resource) {
if (IGNORED_TYPES.includes(type)) {
return;
}
const stacks = captureStackTraces().slice(1);
asyncResources.set(asyncId, {
type,
resource,
stacks
});
},
destroy (asyncId) {
asyncResources.delete(asyncId);
}
});
hook.enable();
function whyIsNodeRunning (logger = console) {
hook.disable();
const activeAsyncResources = Array.from(asyncResources.values())
.filter(({ resource }) => resource.hasRef?.() ?? true);
logger.error(`There are ${activeAsyncResources.length} handle(s) keeping the process running.`);
for (const asyncResource of activeAsyncResources) {
printStacks(asyncResource, logger);
}
}
function printStacks (asyncResource, logger) {
const stacks = asyncResource.stacks.filter((stack) => {
const fileName = stack.getFileName();
return fileName !== null && !fileName.startsWith('node:');
});
logger.error('');
logger.error(`# ${asyncResource.type}`);
if (!stacks[0]) {
logger.error('(unknown stack trace)');
return;
}
const maxLength = stacks.reduce((length, stack) => Math.max(length, formatLocation(stack).length), 0);
for (const stack of stacks) {
const location = formatLocation(stack);
const padding = ' '.repeat(maxLength - location.length);
try {
const lines = readFileSync(normalizeFilePath(stack.getFileName()), 'utf-8').split(/\n|\r\n/);
const line = lines[stack.getLineNumber() - 1].trim();
logger.error(`${location}${padding} - ${line}`);
} catch (e) {
logger.error(`${location}${padding} ${e}`);
}
}
}
function formatLocation (stack) {
const filePath = formatFilePath(stack.getFileName());
return `${filePath}:${stack.getLineNumber()}`;
}
function formatFilePath (filePath) {
const absolutePath = normalizeFilePath(filePath);
const relativePath = relative(process.cwd(), absolutePath);
return relativePath.startsWith('..') ? absolutePath : relativePath;
}
function normalizeFilePath (filePath) {
return filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath;
}
// See: https://v8.dev/docs/stack-trace-api
function captureStackTraces () {
const target = {};
const original = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stackTraces) => stackTraces; // eslint-disable-line
Error.captureStackTrace(target, captureStackTraces);
const capturedTraces = target.stack;
Error.prepareStackTrace = original;
return capturedTraces;
}
const v8 = require('node:v8');
// We want to terminate on errors, not throw up a dialog
@@ -279,42 +169,8 @@ app.whenReady().then(async () => {
process.exit(1);
}
const fifteenMinutes = 15 * 60 * 1000;
const testTimeout = setTimeout(async () => {
console.log('Electron tests timed out after 15 minutes.');
if (process.platform === 'win32') {
const scArgs = [
'screen.png'
];
const ARTIFACT_DIR = path.join(__dirname, 'artifacts');
fs.mkdirSync(ARTIFACT_DIR, { recursive: true });
const { stdout, stderr } = childProcess.spawnSync(path.resolve(__dirname, '..', 'script', 'screenCapture.bat'), scArgs, {
cwd: ARTIFACT_DIR,
stdio: 'inherit'
});
console.log(`screenCap: ${stdout} ${stderr}`);
}
process.exit(1);
}, fifteenMinutes);
const cb = () => {
console.log('In SPEC CB');
clearTimeout(testTimeout);
console.log(`In SPEC CB, process next tick with failures: ${runner.failures} for ${process.platform}`);
if (process.platform === 'win32') {
const scBat = path.resolve(__dirname, '..', 'script', 'screenCapture.bat');
/* const scArgs = [
'screen.png'
]; */
const ARTIFACT_DIR = path.join(__dirname, 'spec', 'artifacts');
fs.mkdirSync(ARTIFACT_DIR, { recursive: true });
const { stdout, stderr } = childProcess.spawnSync(scBat, [], {
cwd: ARTIFACT_DIR
});
console.log(`screenCap results ${stdout} ${stderr}`);
}
setImmediate(() => whyIsNodeRunning());
process.exit(runner.failures);
};

View File

@@ -3,16 +3,128 @@ import { BrowserWindow } from 'electron/main';
import { AssertionError } from 'chai';
import { SuiteFunction, TestFunction } from 'mocha';
import { createHook } from 'node:async_hooks';
import * as childProcess from 'node:child_process';
import { readFileSync } from 'node:fs';
import * as http from 'node:http';
import * as http2 from 'node:http2';
import * as https from 'node:https';
import * as net from 'node:net';
import * as path from 'node:path';
import { relative } from 'node:path';
import { setTimeout } from 'node:timers/promises';
import * as url from 'node:url';
import { fileURLToPath } from 'node:url';
import * as v8 from 'node:v8';
const IGNORED_TYPES = [
'TIMERWRAP',
'PROMISE',
'PerformanceObserver',
'RANDOMBYTESREQUEST'
];
let asyncResources:Map<number, any>;
let hook:any;
export function initWhyIsNodeRunning () {
asyncResources = new Map();
hook = createHook({
init (asyncId, type, triggerAsyncId, resource) {
if (IGNORED_TYPES.includes(type)) {
return;
}
const stacks = captureStackTraces().slice(1);
asyncResources.set(asyncId, {
type,
resource,
stacks
});
},
destroy (asyncId) {
asyncResources.delete(asyncId);
}
});
hook.enable();
}
export function whyIsNodeRunning () {
hook.disable();
const activeAsyncResources = Array.from(asyncResources.values())
.filter(({ resource }) => resource.hasRef?.() ?? true);
console.error(`There are ${activeAsyncResources.length} handle(s) keeping the process running.`);
for (const asyncResource of activeAsyncResources) {
printStacks(asyncResource, console);
}
}
function printStacks (asyncResource:any, logger:any) {
const stacks = asyncResource.stacks.filter((stack:any) => {
const fileName = stack.getFileName();
return fileName !== null && !fileName.startsWith('node:internal/async_hooks');
});
logger.error('');
logger.error(`# ${asyncResource.type}`);
if (!stacks[0]) {
logger.error('(unknown stack trace)');
return;
}
const maxLength = stacks.reduce((length:any, stack:any) => Math.max(length, formatLocation(stack).length), 0);
for (const stack of stacks) {
const location = formatLocation(stack);
const padding = ' '.repeat(maxLength - location.length);
try {
const lines = readFileSync(normalizeFilePath(stack.getFileName()), 'utf-8').split(/\n|\r\n/);
const line = lines[stack.getLineNumber() - 1].trim();
logger.error(`${location}${padding} - ${line}`);
} catch {
logger.error(`${location}${padding}`);
}
}
}
function formatLocation (stack:any) {
const filePath = formatFilePath(stack.getFileName());
return `${filePath}:${stack.getLineNumber()}`;
}
function formatFilePath (filePath:any) {
const absolutePath = normalizeFilePath(filePath);
const relativePath = relative(process.cwd(), absolutePath);
return relativePath.startsWith('..') ? absolutePath : relativePath;
}
function normalizeFilePath (filePath:any) {
return filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath;
}
// See: https://v8.dev/docs/stack-trace-api
function captureStackTraces () {
const target:any = {};
const original = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stackTraces) => stackTraces; // eslint-disable-line
Error.captureStackTrace(target, captureStackTraces);
const capturedTraces = target.stack;
Error.prepareStackTrace = original;
return capturedTraces;
}
const addOnly = <T>(fn: Function): T => {
const wrapped = (...args: any[]) => {
return fn(...args);

View File

@@ -1284,16 +1284,19 @@ describe('<webview> tag', function () {
it('sets the referrer url', async () => {
const referrer = 'http://github.com/';
const received = await new Promise<string | undefined>((resolve, reject) => {
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
try {
resolve(req.headers.referer);
} catch (e) {
reject(e);
} finally {
res.end();
server.close();
}
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
listen(server).then(({ url }) => {
loadWebView(w, {
httpreferrer: referrer,
@@ -1540,7 +1543,7 @@ describe('<webview> tag', function () {
describe('did-redirect-navigation event', () => {
it('is emitted on redirects', async () => {
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
if (req.url === '/302') {
res.setHeader('Location', '/200');
res.statusCode = 302;
@@ -1550,7 +1553,10 @@ describe('<webview> tag', function () {
}
});
const { url } = await listen(server);
defer(() => { server.close(); });
defer(() => {
server.close();
server = null as unknown as http.Server;
});
const event = await loadWebViewAndWaitForEvent(w, {
src: `${url}/302`
}, 'did-redirect-navigation');
@@ -1695,11 +1701,15 @@ describe('<webview> tag', function () {
describe('dom-ready event', () => {
it('emits when document is loaded', async () => {
const server = http.createServer(() => {});
let server = http.createServer(() => {});
const { port } = await listen(server);
await loadWebViewAndWaitForEvent(w, {
src: `file://${fixtures}/pages/dom-ready.html?port=${port}`
}, 'dom-ready');
defer(() => {
server.close();
server = null as unknown as http.Server;
});
});
itremote('throws a custom error when an API method is called before the event is emitted', () => {
@@ -2186,7 +2196,7 @@ describe('<webview> tag', function () {
it('should authenticate with correct credentials', async () => {
const message = 'Authenticated';
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
const credentials = auth(req)!;
if (credentials.name === 'test' && credentials.pass === 'test') {
res.end(message);
@@ -2196,6 +2206,7 @@ describe('<webview> tag', function () {
});
defer(() => {
server.close();
server = null as unknown as http.Server;
});
const { port } = await listen(server);
const e = await loadWebViewAndWaitForEvent(w, {