test: fix retry-reliant webview and debugger specs (#55227)

debugger.sendCommand "does not crash for invalid unicode characters in
message" attached to a window that had never loaded anything and sent
Network.enable without waiting for it. With no renderer yet, the command
only reached the renderer during the first navigation, and
Network.loadingFinished was sometimes never reported. Load about:blank
first, attach, await Network.enable, then navigate.

<webview>.reload() "should emit beforeunload handler": the guest has no
user gesture, so Chromium skips the beforeunload confirmation and the
reload goes ahead. The fixture's IPC, deferred with setTimeout(0) in the
hidden window, could be dropped when the document unloaded. Send it
synchronously from the handler.

<webview> "follows the embedder window when it is hidden and shown
again" took the guest's next visibility report as the state to check, but
the guest can report 'hidden' before the embedder has shown it. Wait for
each expected state instead, and run the suite serially like the
BrowserWindow visibilityState specs, since on macOS a window from another
spec worker can occlude the embedder.


Claude-Session: https://claude.ai/code/session_01LPMvJBQiyP6b7QjatACmnS

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
claude[bot]andClaude authored and GitHub committed 2026-10-26 08:01:47 +00:00
1 parent 6408a0c6bb
commit 0187553fce
3 files changed
+38 -25

No files matched your search

+15 -14
View File
@@ -235,27 +235,28 @@ describe('debugger module', () => {
});
it('does not crash for invalid unicode characters in message', async () => {
w.webContents.debugger.attach();
const loadingFinished = new Promise<void>((resolve) => {
w.webContents.debugger.on('message', (event, method) => {
// loadingFinished indicates that page has been loaded and it did not
// crash because of invalid UTF-8 data
if (method === 'Network.loadingFinished') {
resolve();
}
});
});
server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('\uFFFF');
});
const { url } = await listen(server);
w.webContents.debugger.sendCommand('Network.enable');
w.loadURL(url);
// Network.enable only reaches the renderer once it exists. On a fresh
// window that is during the first navigation, which can then finish
// before the renderer reports Network.loadingFinished. Load a page
// first and wait for Network.enable before the real navigation.
await w.loadURL('about:blank');
w.webContents.debugger.attach();
await w.webContents.debugger.sendCommand('Network.enable');
// loadingFinished indicates that page has been loaded and it did not
// crash because of invalid UTF-8 data
const loadingFinished = emittedUntil(
w.webContents.debugger,
'message',
(_event: any, method: string) => method === 'Network.loadingFinished'
);
await w.loadURL(url);
await loadingFinished;
});
+5 -4
View File
@@ -2,10 +2,11 @@
<body>
<script type="text/javascript" charset="utf-8">
window.onbeforeunload = function() {
setTimeout(function() {
var ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.sendToHost('onbeforeunload');
}, 0);
// Report synchronously. Without a user gesture Chromium skips the
// confirmation and the navigation goes ahead, so a deferred message can
// be dropped when this document unloads.
var ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.sendToHost('onbeforeunload');
return false;
}
</script>
+18 -7
View File
@@ -230,37 +230,48 @@ describe('<webview> tag', () => {
});
});
ifdescribe(process.platform !== 'win32')('document.visibilityState/hidden', () => {
// The guest's visibility follows whether the embedder window is actually on
// screen, so this needs the screen to itself (see the BrowserWindow
// visibilityState specs): on macOS a window from another spec worker can
// occlude this one and hide the guest.
ifdescribe(process.platform !== 'win32')('document.visibilityState/hidden', { tags: ['serial'] }, () => {
afterEach(() => {
ipcMain.removeAllListeners('pong');
});
afterEach(closeAllWindows);
// The guest reports its state when its script runs and on every
// visibilitychange. Wait for the expected state rather than taking the
// next report: the guest can run its script while it is still being
// attached, before the embedder has shown it, and report 'hidden' first.
const guestVisibility = (state: 'visible' | 'hidden') =>
emittedUntil(ipcMain, 'pong', (_event: unknown, visibilityState: string) => visibilityState === state);
it('follows the embedder window when it is hidden and shown again', async () => {
const w = new BrowserWindow({
width: 100,
height: 100,
webPreferences: { webviewTag: true, nodeIntegration: true, contextIsolation: false }
});
const initiallyVisible = guestVisibility('visible');
w.loadFile(path.join(fixtures, 'pages', 'webview-visibilitychange.html'));
{
const [, visibilityState, hidden] = await once(ipcMain, 'pong');
expect(visibilityState).toBe('visible');
const [, , hidden] = await initiallyVisible;
expect(hidden).toBe(false);
}
const hiddenState = guestVisibility('hidden');
w.hide();
{
const [, visibilityState, hidden] = await once(ipcMain, 'pong');
expect(visibilityState).toBe('hidden');
const [, , hidden] = await hiddenState;
expect(hidden).toBe(true);
}
const visibleState = guestVisibility('visible');
w.show();
{
const [, visibilityState, hidden] = await once(ipcMain, 'pong');
expect(visibilityState).toBe('visible');
const [, , hidden] = await visibleState;
expect(hidden).toBe(false);
}
});