Compare commits

...

1 Commits

Author SHA1 Message Date
jackwener d53d2a3c76 Fix automation window not closing on command failure
The error path in executeCommand did not call page.closeWindow(),
leaving the automation window open until the extension's idle timer
fires. On Windows, MV3 service worker suspension makes this timer
unreliable, causing windows to linger indefinitely.

Now closeWindow is called after diagnostic collection but before
rethrowing, ensuring the window is closed on both success and failure.
2026-04-13 16:41:47 +08:00
2 changed files with 34 additions and 1 deletions
+29
View File
@@ -4,6 +4,8 @@ import { executeCommand, prepareCommandArgs } from './execution.js';
import { TimeoutError } from './errors.js';
import { cli, Strategy } from './registry.js';
import { withTimeoutMs } from './runtime.js';
import * as runtime from './runtime.js';
import * as capRouting from './capabilityRouting.js';
describe('executeCommand — non-browser timeout', () => {
it('applies timeoutSeconds to non-browser commands', async () => {
@@ -46,6 +48,33 @@ describe('executeCommand — non-browser timeout', () => {
).rejects.toThrow('sentinel timeout');
});
it('calls closeWindow on browser command failure', async () => {
const closeWindow = vi.fn().mockResolvedValue(undefined);
const mockPage = { closeWindow } as any;
// Mock shouldUseBrowserSession to return true
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
// Mock browserSession to invoke the callback with our mock page
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => {
return fn(mockPage);
});
const cmd = cli({
site: 'test-execution',
name: 'browser-close-on-error',
description: 'test closeWindow on failure',
browser: true,
strategy: Strategy.PUBLIC,
func: async () => { throw new Error('adapter failure'); },
});
await expect(executeCommand(cmd, {})).rejects.toThrow('adapter failure');
expect(closeWindow).toHaveBeenCalledTimes(1);
vi.restoreAllMocks();
});
it('does not re-run custom validation when args are already prepared', async () => {
const validateArgs = vi.fn();
const cmd: CliCommand = {
+5 -1
View File
@@ -226,13 +226,17 @@ export async function executeCommand(
await page.closeWindow?.().catch(() => {});
return result;
} catch (err) {
// Collect diagnostic while page is still alive (before browserSession closes it).
// Collect diagnostic while page is still alive (before closing the window).
if (isDiagnosticEnabled()) {
const internal = cmd as InternalCliCommand;
const ctx = await collectDiagnostic(err, internal, page);
emitDiagnostic(ctx);
diagnosticEmitted = true;
}
// Close the automation window on failure too — without this, the window
// lingers until the extension's idle timer fires (unreliable on Windows
// where MV3 service workers may be suspended before setTimeout triggers).
await page.closeWindow?.().catch(() => {});
throw err;
}
}, { workspace: `site:${cmd.site}`, cdpEndpoint });