From 364b5955acbdf405ffdcc47ca4508037cf389c11 Mon Sep 17 00:00:00 2001 From: ggbdpq Date: Fri, 9 Oct 2026 00:24:51 +0800 Subject: [PATCH] test(desktop): stub the workbar menu popup in the WorkHub layout e2e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec opened a real native menu for the workbar's add-panel button and then closed it with a scripted closePopup guarded by a main-process workbarMenuOpen flag. On Linux CI the GTK popup can already be torn down (e.g. after a window resize) while menu-will-close has not reached JS, so the stale flag still holds and closePopup crashes the Electron main process; closePopup could also crash on a live popup in that window setup. This intermittently killed the "Desktop e2e" step on byte-identical trees that had already passed CI (#5995, failure 1). Every assertion in this segment is Maka state — aria-expanded, the dock backdrop, and the WorkHub WebContentsView's native visibility — none of which needs a real GTK menu. Stub Menu.prototype.popup so it records the probe and restores itself immediately (the later task-action menu keeps the real popup), simulate the close through the menu's own menu-will-close lifecycle event, and drive the popup callback from it so the renderer's request resolves and aria-expanded drops through the same path a real menu uses. This removes closePopup, and both crash paths, from the test. Product code never calls closePopup, so the change is test-only. Verified on macOS arm64: the spec passes before (14.8s) and after (12.0s single / 28.2s full file, 3 passed) the change, with biome check and check:asf-headers clean. The crash itself is Linux-only and was not reproduced locally; the fix removes the native popup/closePopup path the crash rides on. Addresses #5995 (failure 1 of 3; the issue asks for the three failures to be fixed and closed separately). Generated-by: GLM-5.3-Flash (ZCode) --- apps/desktop/e2e/workhub-layout.spec.ts | 38 +++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/desktop/e2e/workhub-layout.spec.ts b/apps/desktop/e2e/workhub-layout.spec.ts index 7b438bd2c1a..6162d9e7eef 100644 --- a/apps/desktop/e2e/workhub-layout.spec.ts +++ b/apps/desktop/e2e/workhub-layout.spec.ts @@ -116,21 +116,31 @@ test('WorkHub uses its coordination model and shared attachment composer', async // would be covered by this WebContentsView and never receive native clicks. await workhub.getByRole('button', { name: '展开任务工作栏', exact: true }).click(); await expect(page.locator('.maka-session-workbar[data-placement="right"]')).toBeVisible(); - // A real native menu must coexist with the live sibling WebContentsView. - // DOM tests cannot detect replacing that view with a frozen screenshot. + // The menu interaction must coexist with the live sibling WebContentsView. + // DOM tests cannot detect replacing that view with a frozen screenshot, so + // the WorkHub view's native visibility is asserted below. The popup itself + // is stubbed instead of really opened: on Linux the native teardown races + // the scripted close and crashes the main process (#5995), and every + // assertion here is Maka state — aria-expanded, the dock backdrop, the + // view's visibility — none of which needs a real GTK menu. await app.evaluate(({ Menu }) => { const original = Menu.prototype.popup; Menu.prototype.popup = function (options) { const probe = globalThis as unknown as { workbarMenu: Electron.Menu; workbarMenuOpen: boolean }; probe.workbarMenu = this; probe.workbarMenuOpen = true; - // Track the popup's own lifetime in the main process: Linux may - // auto-dismiss it (e.g. after a resize) at any moment. + // Restoring immediately keeps the later task-action menu on the real + // native popup; only this scripted one is simulated. + Menu.prototype.popup = original; + // The popup contract is that options.callback fires once the menu is + // closed; that callback resolves the renderer's request and drops + // aria-expanded. Driving it from the menu's own lifecycle event lets + // the test close the menu without ever touching native closePopup. this.once('menu-will-close', () => { probe.workbarMenuOpen = false; + (options as { callback?: () => void } | undefined)?.callback?.(); }); - Menu.prototype.popup = original; - return original.call(this, options); + return undefined; }; }); const addPanel = page.getByRole('button', { name: '添加面板', exact: true }); @@ -142,16 +152,14 @@ test('WorkHub uses its coordination model and shared attachment composer', async 'webContents' in child && (child as Electron.WebContentsView).webContents.getURL().includes('surface=workhub'))); return container?.getVisible(); })).toBe(true); - // A menu that already auto-dismissed (Linux closes popups after window - // resizes) must not be closed again: closePopup on a dead popup crashes the - // main process. Decide inside the main process, in the same task as the - // close, so the dismissal cannot land between the check and the call; - // checking the renderer's aria-expanded first left exactly that window. - await mainWindow.evaluate((window) => { + // Close through the menu's own lifecycle event. The stubbed popup never + // opened a native menu, so there is nothing for closePopup to close — and + // closePopup on a popup GTK already tore down crashes the main process + // (#5995). Decide inside the main process, in the same task as the emit, + // so the close cannot land before the state behind the assertions above. + await mainWindow.evaluate(() => { const probe = globalThis as unknown as { workbarMenu: Electron.Menu; workbarMenuOpen: boolean }; - // Close on the same owner passed to popup(). The no-window overload takes - // Electron's close-all MenuRunner path on Linux, even for this single menu. - if (probe.workbarMenuOpen) probe.workbarMenu.closePopup(window); + if (probe.workbarMenuOpen) probe.workbarMenu.emit('menu-will-close'); }); await expect(addPanel).not.toHaveAttribute('aria-expanded', 'true'); await workhub.getByRole('button', { name: '收起任务工作栏', exact: true }).click();