build: use github actions for windows (#44136)

* build: test windows runner

* build: try build windows on windows?

* build: take win/cross changes

* build: use bash as default shell always

* build: configure git for windows build tools

* build: bash as default

* build: configure windows correctly

* build: use sha1sum

* build: force windows cipd init and python3 existence

* just pain

* build: restore cache on windows

* build: use build-tools gclient

* build: sync gclient vars to build windows job

* build: output depshash for debugging

* build: past sam was a silly goose

* build: depshash logging

* build: force lf endings for lock and DEPS

* build: platform strings are hard

* build: checkout on windows host

* sup

* no check

* idk

* sigh

* ...

* no double checkout

* build: yolo some stuff

* build: run gn-check for windows on linux hosts for speed

* use container...

* cry ?

* build: e d

* e d

* no log

* fix toolchain on windows cross check

* build: use powershell to add mksnapshot_args

* build: enable x86 and arm64 windows builds too

* clean up

* maybe not needed

* build: keep action around for post step

* build: configure git global on win

* build: ia32 zip manifest

* build: no patch depot_tools for tests

* build: get arm64 windows closer to working

* build: windows tar is ass

* 32 bit on 32 bit

* maybe bash

* build: set up nodejs

* correct windows sharding

* fix some spec runner stuff

* fix windows tests

* overwrite -Force

* sigh

* screen res

* wat

* logs

* ... more logs

* line endings will be the death of me

* remove 1080p force thing

* vsctools + logging

* disable some fullscreen tests on GHA

* no progress

* run all CI

* install visual studio on arm64

* windows hax for non windows

* maybe arm sdk

* clean up depshash logic

* build: use single check per platform

* ensure clean args

* fix loop

* remove debug

* update default build image sha for dispatch

* plzzzz

* one more try

* arm64 vctools

* sad

* build: fix non-dispatch windows gn check

* chore: debug datadog-ci location

* chore: update build-tools for newer toolchain

* chore: set path for datadog-ci

* try this

* chore: fixup gn check

* fixup gn-check some more

* fixup windows gn check

* chore: fixup windows gn check

* test: use cmd for Windows testing

* fixup use cmd for testing on Windows

* fixup windows GN check

* fixup npm config arch for x86

* Can we set test files via powershell

* fixup to set test files via powershell

* fixup set test files via powershell

* Don't check cross instance cache disk space on Windows

* Use separate step to set env variables for testing

* fixup Use separate step to set env variables for testing

* fixup Use separate step to set env variables for testing

* fixup Use separate step to set env variables for testing (AGAIN)

* use powershell if in powershell

* fixup use powershell if in powershell

* chore: remove no longer needed changes to depot_tools

xref: 5669094
and 5844046

* chore: try using 7zip on Windows to extract tarball

* Revert "chore: try using 7zip on Windows to extract tarball"

This reverts commit c7432b6a37857fd0746b8f1776fbd1103dba0b85.

* test: debug failing tests on GHA windows

* fix: ftbfs when including simdjson in Node.js

(cherry picked from commit 48e44c40d61b7aa843a990d4e0c8dec676b4ce8f)

* chore: try to track down Windows testing hang

* use correct timeout

* try this

* see if this helps

* try to figure out why node is running

* shard tests to try to narrow down WOA lockup

* try to narrow down problem test

* Narrow down blocking test more

* do we need a combo to repro

* see if this cleans up the tests

* fixup navigator.usb test

* remove logging from problematic tests

* Revert "shard tests to try to narrow down WOA lockup"

This reverts commit a1806583769678491814cb8b008131c32be4e8fb.

* remove logging

* debug keyboard test

* add timeout for Windows since arm64 sometimes hangs

* see if this helps

* put back original timeout

* try to use screenCapture to get screenshots of what is going on on WOA

* try using electron screencapture to debug WOA hang

* chore: turn off privacy experience

* run screenshot on both shards

* fixup screencap

* try to narrow down hanging spec

* chore: cleanup servers left open

* cleanup tests

* Revert "try to narrow down hanging spec"

This reverts commit a0f959f5382f4012a9919ac535d42c5333eb7d5f.

* cleanup test debugging

* fixup extensions spec

* cleanup unneeded items

* run wtf with 2 shards instead of 6

* Revert "run wtf with 2 shards instead of 6"

This reverts commit ca2d282129ee42c535d80f9876d6fa0dc6c08344.

* debug windows version on woa

* dump more info

* Get detailed CPU info

* revert debugging

* use same args as AppVeyor WOA for GHA WOA

* fixup use same args as AppVeyor WOA for GHA WOA

* fixup use same args as AppVeyor WOA for GHA WOA

* try to track down which tests trigger hang

* one or more of these combinations should hang

* break up web contents spec to find hang

* further break down api-web-contents to find hang

* test: ensure all webContents are closed

* test: fix require is not defined error

* see if api-web-contents spec is now good

* test: ensure all webContents are closed

* Revert "try to track down which tests trigger hang"

This reverts commit 07298d6ffeb4873ef7615a8ec3d1a6696e354ff4.

* chore: use alternate location for windows toolchain

* Reapply "try to track down which tests trigger hang"

This reverts commit 0321f76d01069ef325339b6fe6ed39700eae2b6b.

* try to narrow down problem test

* fix TEST_SHARD env var

* no, really fix TEST_SHARD env var

* see if this fixes it

* test: cleanup any remaining windows and webcontents

* see if new cleanup helps

* dont destroy webcontents for now

* fixup dont destroy webcontents for now

* Only cleanup right before process.exit

* see if this fixes the hang

* actually destroy webcontents

* Revert "Reapply "try to track down which tests trigger hang""

This reverts commit cdee7de049ce6bb5f67bbcc64882c56aa2c73027.

* see if this helps

* Revert "see if this helps"

This reverts commit 9a15a69cf7dbc456db7a61efa5b6870535bae993.

* Is it all about the web contents?

* it is all about the webcontents

but which one?

* Narrow down problem webcontents test

* try to speed up git install on WOA

* disable problematic test on WOA

* remove debugging

* remove debugging from choco installs

* Revert "disable problematic test on WOA"

This reverts commit e060fb0839b73d53cfde1f8acdca634f8e267937.

* Revert "remove debugging"

This reverts commit f18dd8b1a555f56bb06d0ea996a6eff31b424bf1.

* run against all the tests in the failing shard

* don't run visibility tests first

* remove debugging

* 3 is a magic number

* Revert "3 is a magic number"

This reverts commit 36b91ccf9f03a4b34230cd69ceca482f7d8428c1.

* match what Appveyor runs exactly

* Revert "match what Appveyor runs exactly"

This reverts commit 7260dd432216c62696e4bc864930f17c857eabbe.

* chore: sort files alphabetically

* find out what spec is leaving stuff open

* chore: Checkout PR HEAD commit

 instead of merge commit

* try using app.exit instead of process.exit

* test: cleanup BrowserWindows and webContents

* Revert "chore: sort files alphabetically"

This reverts commit d9e217ffb1522076e150fce9e43a31bf56716acb.

* chore: use win32 to match process.platform

Needed for build-tools to download from PRs

* chore: cache yarn dir

* fixup cache yarn

* fixup use win32 to match process.platform

* fixup use win32 to match process.platform

* fixup cache yarn

* Add debugging for WOA hang

* Add debugging for failing keyboard lock test

* Revert "Add debugging for WOA hang"

This reverts commit 8df03d568d15a269e4026140d1158e8cdf551dec.

* try using process.kill

* add more debugging to keyboard.lock test

* Revert "Add debugging for failing keyboard lock test"

* remove debugging

* test: disable keyboard.lock on Windows

* test: disable fullscreen tests on Windows

* test: only force test suite exit on WOA

* fixup test: only force test suite exit on WOA

* cleanup tests

* extract yarn caching/install to action

* try using bash to run windows tests

* remove left over debugging

* standardize on 'win' for Windows builds

* use 'x86' for arch for manifest files

* fixup try using bash to run windows tests

* fixup use 'x86' for arch for manifest files

* standardize on 'win' for Windows builds

* fixup use 'x86' for arch for manifest files

* fixup try using bash to run windows tests

---------

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
Samuel Attard 2024-12-12 08:51:24 -08:00 committed by GitHub
parent a5b4339884
commit be1a3dce83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 657 additions and 328 deletions

View file

@ -2159,6 +2159,10 @@ describe('default behavior', () => {
serverUrl = (await listen(server)).url;
});
after(() => {
server.close();
});
it('should emit a login event on app when a WebContents hits a 401', async () => {
const w = new BrowserWindow({ show: false });
w.loadURL(serverUrl);

View file

@ -935,6 +935,9 @@ describe('BrowserWindow module', () => {
});
url = (await listen(server)).url;
});
after(() => {
server.close();
});
it('for initial navigation, event order is consistent', async () => {
const firedEvents: string[] = [];
const expectedEventOrder = [

View file

@ -858,6 +858,9 @@ describe('session module', () => {
res.end('authenticated');
}
});
defer(() => {
server.close();
});
const { port } = await listen(server);
const fetch = (url: string) => new Promise((resolve, reject) => {
const request = net.request({ url, session: ses });
@ -941,6 +944,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', async () => {
const willDownload = once(session.defaultSession, 'will-download');
session.defaultSession.downloadURL(`${url}:${port}`);
@ -951,7 +961,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;
@ -1013,7 +1023,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;
@ -1057,6 +1067,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', async () => {
const w = new BrowserWindow({ show: false });
const willDownload = once(w.webContents.session, 'will-download');
@ -1068,7 +1085,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;
@ -1124,7 +1141,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;
@ -1315,6 +1332,9 @@ describe('session module', () => {
send(req, req.url!, options)
.on('error', (error: any) => { throw error; }).pipe(res);
});
defer(() => {
rangeServer.close();
});
try {
const { url } = await listen(rangeServer);
const w = new BrowserWindow({ show: false });

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

@ -13,7 +13,7 @@ import { setTimeout } from 'node:timers/promises';
import * as url from 'node:url';
import { ifdescribe, defer, waitUntil, listen, ifit } from './lib/spec-helpers';
import { closeAllWindows } from './lib/window-helpers';
import { cleanupWebContents, closeAllWindows } from './lib/window-helpers';
const fixturesPath = path.resolve(__dirname, 'fixtures');
const features = process._linkedBinding('electron_common_features');
@ -63,6 +63,7 @@ describe('webContents module', () => {
});
describe('fromFrame()', () => {
afterEach(cleanupWebContents);
it('returns WebContents for mainFrame', () => {
const contents = (webContents as typeof ElectronInternal.WebContents).create();
expect(webContents.fromFrame(contents.mainFrame)).to.equal(contents);
@ -85,6 +86,7 @@ describe('webContents module', () => {
});
describe('fromDevToolsTargetId()', () => {
afterEach(closeAllWindows);
it('returns WebContents for attached DevTools target', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
@ -103,7 +105,10 @@ describe('webContents module', () => {
});
describe('will-prevent-unload event', function () {
afterEach(closeAllWindows);
afterEach(async () => {
await closeAllWindows();
await cleanupWebContents();
});
it('does not emit if beforeunload returns undefined in a BrowserWindow', async () => {
const w = new BrowserWindow({ show: false });
w.webContents.once('will-prevent-unload', () => {
@ -305,11 +310,13 @@ describe('webContents module', () => {
]);
let w: BrowserWindow;
before(async () => {
beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: false } });
await w.loadURL('about:blank');
});
after(closeAllWindows);
afterEach(async () => {
await closeAllWindows();
});
it('resolves the returned promise with the result', async () => {
const result = await w.webContents.executeJavaScript(code);
@ -380,6 +387,8 @@ describe('webContents module', () => {
await w.loadURL('about:blank');
});
after(() => w.close());
it('resolves the returned promise with the result', async () => {
await w.webContents.executeJavaScriptInIsolatedWorld(999, [{ code: 'window.X = 123' }]);
const isolatedResult = await w.webContents.executeJavaScriptInIsolatedWorld(999, [{ code: 'window.X' }]);
@ -391,6 +400,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 +511,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 +540,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 +563,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 () => {
@ -820,6 +834,7 @@ describe('webContents module', () => {
});
describe('isFocused() API', () => {
afterEach(closeAllWindows);
it('returns false when the window is hidden', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
@ -1177,6 +1192,7 @@ describe('webContents module', () => {
});
describe('startDrag({file, icon})', () => {
afterEach(closeAllWindows);
it('throws errors for a missing file or a missing/empty icon', () => {
const w = new BrowserWindow({ show: false });
expect(() => {
@ -1281,6 +1297,7 @@ describe('webContents module', () => {
});
describe('userAgent APIs', () => {
afterEach(closeAllWindows);
it('is not empty by default', () => {
const w = new BrowserWindow({ show: false });
const userAgent = w.webContents.getUserAgent();
@ -1311,6 +1328,7 @@ describe('webContents module', () => {
});
describe('audioMuted APIs', () => {
afterEach(closeAllWindows);
it('can set the audio mute level (functions)', () => {
const w = new BrowserWindow({ show: false });
@ -1526,6 +1544,9 @@ describe('webContents module', () => {
res.end();
});
});
defer(() => {
server.close();
});
listen(server).then(({ url }) => {
const content = `<iframe src=${url}></iframe>`;
w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
@ -1538,8 +1559,6 @@ describe('webContents module', () => {
done();
} catch (e) {
done(e);
} finally {
server.close();
}
}
});
@ -2023,12 +2042,13 @@ describe('webContents module', () => {
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();
});
listen(server).then(({ url }) => {
w.webContents.once('did-finish-load', () => {
w.webContents.setWindowOpenHandler(details => {
@ -2055,6 +2075,9 @@ describe('webContents module', () => {
}
res.end('');
});
defer(() => {
server.close();
});
listen(server).then(({ url }) => {
w.webContents.once('did-finish-load', () => {
w.webContents.setWindowOpenHandler(details => {
@ -2823,7 +2846,10 @@ describe('webContents module', () => {
});
describe('close() method', () => {
afterEach(closeAllWindows);
afterEach(async () => {
await closeAllWindows();
await cleanupWebContents();
});
it('closes when close() is called', async () => {
const w = (webContents as typeof ElectronInternal.WebContents).create();

View file

@ -9,7 +9,11 @@ import { defer, ifdescribe, waitUntil } from './lib/spec-helpers';
import { closeAllWindows } from './lib/window-helpers';
describe('WebContentsView', () => {
afterEach(closeAllWindows);
afterEach(async () => {
await closeAllWindows();
const existingWCS = webContents.getAllWebContents();
existingWCS.forEach((contents) => contents.close());
});
it('can be instantiated with no arguments', () => {
// eslint-disable-next-line no-new

View file

@ -495,6 +495,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

@ -77,8 +77,9 @@ describe('webFrame module', () => {
describe('api', () => {
let w: WebContents;
let win: BrowserWindow;
before(async () => {
const win = new BrowserWindow({ show: false, webPreferences: { contextIsolation: false, nodeIntegration: true } });
win = new BrowserWindow({ show: false, webPreferences: { contextIsolation: false, nodeIntegration: true } });
await win.loadURL('data:text/html,<iframe name="test"></iframe>');
w = win.webContents;
await w.executeJavaScript(`
@ -89,6 +90,11 @@ describe('webFrame module', () => {
`);
});
after(() => {
win.close();
win = null as unknown as BrowserWindow;
});
describe('top', () => {
it('is self for top frame', async () => {
const equal = await w.executeJavaScript('isSameWebFrame(webFrame.top, webFrame)');

View file

@ -589,12 +589,12 @@ describe('webRequest module', () => {
it('can be proxyed', async () => {
// Setup server.
const reqHeaders : { [key: string] : any } = {};
const server = http.createServer((req, res) => {
let server = http.createServer((req, res) => {
reqHeaders[req.url!] = req.headers;
res.setHeader('foo1', 'bar1');
res.end('ok');
});
const wss = new WebSocket.Server({ noServer: true });
let wss = new WebSocket.Server({ noServer: true });
wss.on('connection', function connection (ws) {
ws.on('message', function incoming (message) {
if (message === 'foo') {
@ -660,9 +660,12 @@ describe('webRequest module', () => {
});
// Cleanup.
after(() => {
defer(() => {
contents.destroy();
server.close();
server = null as unknown as http.Server;
wss.close();
wss = null as unknown as WebSocket.Server;
ses.webRequest.onBeforeRequest(null);
ses.webRequest.onBeforeSendHeaders(null);
ses.webRequest.onHeadersReceived(null);

View file

@ -653,7 +653,9 @@ describe('chromium features', () => {
expect(size).to.be.a('number');
});
it('should lock the keyboard', async () => {
// TODO: Re-enable for windows on GitHub Actions,
// fullscreen tests seem to hang on GHA specifically
ifit(process.platform !== 'win32' || process.arch === 'arm64')('should lock the keyboard', async () => {
const w = new BrowserWindow({ show: true });
await w.loadFile(path.join(fixturesPath, 'pages', 'modal.html'));
@ -670,8 +672,11 @@ describe('chromium features', () => {
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
await setTimeout(1000);
const openAfter1 = await w.webContents.executeJavaScript('document.getElementById(\'favDialog\').open');
expect(openAfter1).to.be.true();
await expect(waitUntil(async () => {
return await w.webContents.executeJavaScript(
'document.getElementById(\'favDialog\').open'
);
})).to.eventually.be.fulfilled();
expect(w.isFullScreen()).to.be.false();
// Test that with lock, with ESC:
@ -681,7 +686,6 @@ describe('chromium features', () => {
await w.webContents.executeJavaScript(`
document.body.requestFullscreen();
`, true);
await enterFS2;
// Request keyboard lock after window has gone fullscreen
@ -696,8 +700,12 @@ describe('chromium features', () => {
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
await setTimeout(1000);
const openAfter2 = await w.webContents.executeJavaScript('document.getElementById(\'favDialog\').open');
expect(openAfter2).to.be.false();
await expect(waitUntil(async () => {
const openAfter2 = await w.webContents.executeJavaScript(
'document.getElementById(\'favDialog\').open'
);
return (openAfter2 === false);
})).to.eventually.be.fulfilled();
expect(w.isFullScreen()).to.be.true();
});
});
@ -2528,6 +2536,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) => {
@ -2971,7 +2980,9 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
server.close();
});
ifit(process.platform !== 'darwin')('can fullscreen from out-of-process iframes (non-macOS)', async () => {
// TODO: Re-enable for windows on GitHub Actions,
// fullscreen tests seem to hang on GHA specifically
ifit(process.platform !== 'darwin' && (process.platform !== 'win32' || process.arch === 'arm64'))('can fullscreen from out-of-process iframes (non-macOS)', async () => {
const fullscreenChange = once(ipcMain, 'fullscreenChange');
const html =
`<iframe style="width: 0" frameborder=0 src="${crossSiteUrl}" allowfullscreen></iframe>`;
@ -2987,12 +2998,12 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
"document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
);
await setTimeout(500);
const width = await w.webContents.executeJavaScript(
"document.querySelector('iframe').offsetWidth"
);
expect(width).to.equal(0);
await expect(waitUntil(async () => {
const width = await w.webContents.executeJavaScript(
"document.querySelector('iframe').offsetWidth"
);
return width === 0;
})).to.eventually.be.fulfilled();
});
ifit(process.platform === 'darwin')('can fullscreen from out-of-process iframes (macOS)', async () => {
@ -3024,8 +3035,9 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
await once(w, 'leave-full-screen');
});
// TODO(jkleinsc) fix this flaky test on WOA
ifit(process.platform !== 'win32' || process.arch !== 'arm64')('can fullscreen from in-process iframes', async () => {
// TODO: Re-enable for windows on GitHub Actions,
// fullscreen tests seem to hang on GHA specifically
ifit(process.platform !== 'win32' || process.arch === 'arm64')('can fullscreen from in-process iframes', async () => {
if (process.platform === 'darwin') await once(w, 'enter-full-screen');
const fullscreenChange = once(ipcMain, 'fullscreenChange');
@ -3700,13 +3712,6 @@ describe('navigator.usb', () => {
});
await sesWin.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end('<body>');
});
serverUrl = (await listen(server)).url;
const devices = await getDevices();
expect(devices).to.be.an('array').that.is.empty();
});

View file

@ -10,7 +10,7 @@ import * as path from 'node:path';
import { emittedNTimes, emittedUntil } from './lib/events-helpers';
import { ifit, listen, waitUntil } from './lib/spec-helpers';
import { closeAllWindows, closeWindow } from './lib/window-helpers';
import { closeAllWindows, closeWindow, cleanupWebContents } from './lib/window-helpers';
const uuid = require('uuid');
@ -23,6 +23,7 @@ describe('chrome extensions', () => {
let server: http.Server;
let url: string;
let port: number;
let wss: WebSocket.Server;
before(async () => {
server = http.createServer((req, res) => {
if (req.url === '/cors') {
@ -31,7 +32,7 @@ describe('chrome extensions', () => {
res.end(emptyPage);
});
const wss = new WebSocket.Server({ noServer: true });
wss = new WebSocket.Server({ noServer: true });
wss.on('connection', function connection (ws) {
ws.on('message', function incoming (message) {
if (message === 'foo') {
@ -42,8 +43,10 @@ describe('chrome extensions', () => {
({ port, url } = await listen(server));
});
after(() => {
after(async () => {
server.close();
wss.close();
await cleanupWebContents();
});
afterEach(closeAllWindows);
afterEach(() => {
@ -283,6 +286,10 @@ describe('chrome extensions', () => {
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } });
await w.loadURL(url);
});
afterEach(() => {
w.close();
w = null as unknown as BrowserWindow;
});
it('getAcceptLanguages()', async () => {
const result = await exec('getAcceptLanguages');
expect(result).to.be.an('array').and.deep.equal(['en-US', 'en']);
@ -308,6 +315,10 @@ describe('chrome extensions', () => {
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } });
await w.loadURL(url);
});
afterEach(async () => {
w.close();
w = null as unknown as BrowserWindow;
});
it('getManifest()', async () => {
const result = await exec('getManifest');
expect(result).to.be.an('object').with.property('name', 'chrome-runtime');
@ -358,6 +369,11 @@ describe('chrome extensions', () => {
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true, contextIsolation: true } });
});
afterEach(() => {
w.close();
w = null as unknown as BrowserWindow;
});
describe('onBeforeRequest', () => {
async function haveRejectedFetch () {
try {
@ -426,6 +442,7 @@ describe('chrome extensions', () => {
customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-api'));
});
afterEach(closeAllWindows);
it('executeScript', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
@ -492,6 +509,7 @@ describe('chrome extensions', () => {
});
describe('background pages', () => {
afterEach(closeAllWindows);
it('loads a lazy background page when sending a message', async () => {
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page'));
@ -559,8 +577,9 @@ describe('chrome extensions', () => {
describe('devtools extensions', () => {
let showPanelTimeoutId: any = null;
afterEach(() => {
afterEach(async () => {
if (showPanelTimeoutId) clearTimeout(showPanelTimeoutId);
await closeAllWindows();
});
const showLastDevToolsPanel = (w: BrowserWindow) => {
w.webContents.once('devtools-opened', () => {
@ -695,13 +714,14 @@ describe('chrome extensions', () => {
}
});
({ port, url } = await listen(server));
({ port } = await listen(server));
session.defaultSession.loadExtension(contentScript);
});
after(() => {
session.defaultSession.removeExtension('content-script-test');
server.close();
});
beforeEach(() => {
@ -758,10 +778,11 @@ describe('chrome extensions', () => {
});
describe('extension ui pages', () => {
afterEach(() => {
afterEach(async () => {
for (const e of session.defaultSession.getAllExtensions()) {
session.defaultSession.removeExtension(e.id);
}
await closeAllWindows();
});
it('loads a ui page of an extension', async () => {
@ -782,6 +803,7 @@ describe('chrome extensions', () => {
});
describe('manifest v3', () => {
afterEach(closeAllWindows);
it('registers background service worker', async () => {
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const registrationPromise = new Promise<string>(resolve => {
@ -1033,6 +1055,7 @@ describe('chrome extensions', () => {
});
describe('get', () => {
afterEach(closeAllWindows);
it('returns tab properties', async () => {
await w.loadURL(url);
@ -1173,6 +1196,7 @@ describe('chrome extensions', () => {
});
describe('query', () => {
afterEach(closeAllWindows);
it('can query for a tab with specific properties', async () => {
await w.loadURL(url);

View file

@ -1,8 +1,10 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer, webFrame} = require('electron')
ipcRenderer.send('webview-parent-zoom-level', webFrame.getZoomFactor(), webFrame.getZoomLevel())
if (typeof require !== 'undefined') {
const {ipcRenderer, webFrame} = require('electron')
ipcRenderer.send('webview-parent-zoom-level', webFrame.getZoomFactor(), webFrame.getZoomLevel())
}
</script>
</body>
</html>

View file

@ -4,6 +4,8 @@ const fs = require('node:fs');
const path = require('node:path');
const v8 = require('node:v8');
const FAILURE_STATUS_KEY = 'Electron_Spec_Runner_Failures';
// We want to terminate on errors, not throw up a dialog
process.on('uncaughtException', (err) => {
console.error('Unhandled exception in main spec runner:', err);
@ -131,7 +133,7 @@ app.whenReady().then(async () => {
const validTestPaths = argv.files && argv.files.map(file =>
path.isAbsolute(file)
? path.relative(baseElectronDir, file)
: file);
: path.normalize(file));
const filter = (file) => {
if (!/-spec\.[tj]s$/.test(file)) {
return false;
@ -155,17 +157,7 @@ app.whenReady().then(async () => {
const { getFiles } = require('./get-files');
const testFiles = await getFiles(__dirname, filter);
const VISIBILITY_SPEC = ('visibility-state-spec.ts');
const sortedFiles = testFiles.sort((a, b) => {
// If visibility-state-spec is in the list, move it to the first position
// so that it gets executed first to avoid other specs interferring with it.
if (a.indexOf(VISIBILITY_SPEC) > -1) {
return -1;
} else {
return a.localeCompare(b);
}
});
for (const file of sortedFiles) {
for (const file of testFiles.sort()) {
mocha.addFile(file);
}
@ -178,7 +170,12 @@ app.whenReady().then(async () => {
const cb = () => {
// Ensure the callback is called after runner is defined
process.nextTick(() => {
process.exit(runner.failures);
if (process.env.ELECTRON_FORCE_TEST_SUITE_EXIT === 'true') {
console.log(`${FAILURE_STATUS_KEY}: ${runner.failures}`);
process.kill(process.pid);
} else {
process.exit(runner.failures);
}
});
};

View file

@ -16,7 +16,7 @@ export async function copyApp (targetDir: string): Promise<string> {
// On windows and linux we should read the zip manifest files and then copy each of those files
// one by one
const baseDir = path.dirname(process.execPath);
const zipManifestPath = path.resolve(__dirname, '..', '..', 'script', 'zip_manifests', `dist_zip.${process.platform === 'win32' ? 'win' : 'linux'}.${process.arch}.manifest`);
const zipManifestPath = path.resolve(__dirname, '..', '..', 'script', 'zip_manifests', `dist_zip.${process.platform === 'win32' ? 'win' : 'linux'}.${process.arch === 'ia32' ? 'x86' : process.arch}.manifest`);
const filesToCopy = (fs.readFileSync(zipManifestPath, 'utf-8')).split('\n').filter(f => f !== 'LICENSE' && f !== 'LICENSES.chromium.html' && f !== 'version' && f.trim());
await Promise.all(
filesToCopy.map(async rel => {

View file

@ -1,4 +1,4 @@
import { BaseWindow, BrowserWindow } from 'electron/main';
import { BaseWindow, BrowserWindow, webContents } from 'electron/main';
import { expect } from 'chai';
@ -48,8 +48,23 @@ export const closeWindow = async (
}
};
export async function closeAllWindows () {
export async function closeAllWindows (assertNotWindows = false) {
let windowsClosed = 0;
for (const w of BaseWindow.getAllWindows()) {
await closeWindow(w, { assertNotWindows: false });
await closeWindow(w, { assertNotWindows });
windowsClosed++;
}
return windowsClosed;
}
export async function cleanupWebContents () {
let webContentsDestroyed = 0;
const existingWCS = webContents.getAllWebContents();
for (const contents of existingWCS) {
const isDestroyed = once(contents, 'destroyed');
contents.destroy();
await isDestroyed;
webContentsDestroyed++;
}
return webContentsDestroyed;
}

View file

@ -1,4 +1,4 @@
import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, WebContents, WebContentsView } from 'electron/main';
import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, webContents, WebContents, WebContentsView } from 'electron/main';
import { expect } from 'chai';
@ -23,6 +23,8 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
afterEach(async () => {
await closeAllWindows();
w = null as unknown as BrowserWindow;
const existingWCS = webContents.getAllWebContents();
existingWCS.forEach((contents) => contents.close());
});
const load = () => w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));

View file

@ -1700,6 +1700,7 @@ describe('<webview> tag', function () {
await loadWebViewAndWaitForEvent(w, {
src: `file://${fixtures}/pages/dom-ready.html?port=${port}`
}, 'dom-ready');
defer(() => { server.close(); });
});
itremote('throws a custom error when an API method is called before the event is emitted', () => {