From 2c909a301171040ac8402a08391a2ad5e678ffd5 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 19 Oct 2026 19:22:28 +0200 Subject: [PATCH] fix(test): share developer toast handling with Electron browser runs The standalone Electron browser runner bypassed the browser fixture, allowing developer errors to cover Settings. Use the same non-pointer dismissal in both runners while retaining their console logs. --- docs/testing.md | 2 +- packages/app/e2e/support/developer-ui.mjs | 10 ++++++++++ packages/app/e2e/support/fixtures.ts | 12 ++---------- packages/desktop/e2e/browser-tabs.e2e.mjs | 2 ++ 4 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 packages/app/e2e/support/developer-ui.mjs diff --git a/docs/testing.md b/docs/testing.md index 6f70f6af0c..afe7ceade8 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -193,7 +193,7 @@ Test suites in this repo are heavy. Running them in bulk freezes the machine, es - Playwright specs that exercise only the daemon import `daemonTest` from the shared fixtures so they do not create a browser context or page. - Helpers that create projects or workspaces own those records until cleanup. Their clients remove the daemon project on close, and an automatic fixture fails any test that still leaks a project record. Deleting only the temporary directory is not cleanup. Agent helpers pass the intended `workspaceId` through to agent creation; they never infer ownership from `cwd`. - Tests whose subject is daemon-global state, such as an empty history or daemon restart, start a dedicated host explicitly. Filenames and directories describe product behavior, never execution order or isolation mechanics. -- Global setup accepts Metro as ready only when `/status` returns `packager-status:running`, then fetches the document's scripts so the cold bundle compilation finishes before Playwright's per-test timeout starts. A generic TCP listener is not sufficient readiness evidence. The browser suite uses direct local daemon connections and does not start a relay. Automated Metro launches set `EXPO_UNSTABLE_HEADLESS=1` so Expo does not download the native DevTools shell into temporary test homes; its read-only cache breaks teardown. Browser fixtures dispatch Expo's developer-toast close action while retaining console logs. A pointer click moves hover away from the tested control and stalls behind app modal backdrops. +- Global setup accepts Metro as ready only when `/status` returns `packager-status:running`, then fetches the document's scripts so the cold bundle compilation finishes before Playwright's per-test timeout starts. A generic TCP listener is not sufficient readiness evidence. The browser suite uses direct local daemon connections and does not start a relay. Automated Metro launches set `EXPO_UNSTABLE_HEADLESS=1` so Expo does not download the native DevTools shell into temporary test homes; its read-only cache breaks teardown. Browser fixtures and the standalone Electron browser runner share the developer-toast dismissal helper while retaining console logs. A pointer click moves hover away from the tested control and stalls behind app modal backdrops. - The app Playwright harness boots on Windows as well as POSIX. Spawn Node entrypoints through `process.execPath`, not `npx` or `node_modules/.bin` shims: Node refuses to spawn `.cmd` or `.bat` without `shell: true`, and shell mode concatenates argv without escaping and sends kill signals to `cmd.exe` instead of the real child. - Teardown kills the process tree, because a Windows signal reaches only the direct child and leaves forked workers holding the listening port. - The `asdf`-backed local Elixir relay stays POSIX-only, so the `relay-deployment` Playwright project is unavailable on Windows. diff --git a/packages/app/e2e/support/developer-ui.mjs b/packages/app/e2e/support/developer-ui.mjs new file mode 100644 index 0000000000..36d5752688 --- /dev/null +++ b/packages/app/e2e/support/developer-ui.mjs @@ -0,0 +1,10 @@ +/** @param {import('@playwright/test').Page} page */ +export async function installDeveloperToastDismissal(page) { + // Developer chrome must not cover app actions or move the test's pointer/focus. + // Console errors remain in the runner's logs. + const dismiss = page.locator("#error-toast").getByRole("button", { name: "", exact: true }); + await page.addLocatorHandler(dismiss, () => dismiss.dispatchEvent("click"), { + // A new logged error can replace the toast before the old one disappears. + noWaitAfter: true, + }); +} diff --git a/packages/app/e2e/support/fixtures.ts b/packages/app/e2e/support/fixtures.ts index 103387f8c5..aa83df8e91 100644 --- a/packages/app/e2e/support/fixtures.ts +++ b/packages/app/e2e/support/fixtures.ts @@ -1,4 +1,5 @@ import { test as base, expect, type Page } from "@playwright/test"; +import { installDeveloperToastDismissal } from "./developer-ui.mjs"; import { startOutdatedDaemon, type OutdatedDaemon } from "./helpers/daemon-update"; import { startE2EWorker } from "./helpers/e2e-worker"; import { getE2EDaemonPort } from "./helpers/daemon-port"; @@ -130,16 +131,7 @@ const test = daemonTest.extend<{ await ws.close({ code: 1008, reason: "Blocked connection to localhost:6767 during e2e." }); }); - // Expo's development LogBox toast overlaps bottom actions on compact screens. - // Dispatch the developer UI's close action without moving the pointer or - // clicking through an app modal. Console errors still go into the test log. - const dismissDevToast = page - .locator("#error-toast") - .getByRole("button", { name: "", exact: true }); - await page.addLocatorHandler(dismissDevToast, () => dismissDevToast.dispatchEvent("click"), { - // Another logged error can replace the toast before it disappears. - noWaitAfter: true, - }); + await installDeveloperToastDismissal(page); const entries: string[] = []; diff --git a/packages/desktop/e2e/browser-tabs.e2e.mjs b/packages/desktop/e2e/browser-tabs.e2e.mjs index 65879abdde..af142e6385 100644 --- a/packages/desktop/e2e/browser-tabs.e2e.mjs +++ b/packages/desktop/e2e/browser-tabs.e2e.mjs @@ -12,6 +12,7 @@ import { fileURLToPath } from "node:url"; import { experimental_createMCPClient } from "ai"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { chromium } from "playwright"; +import { installDeveloperToastDismissal } from "../../app/e2e/support/developer-ui.mjs"; import { runAppearanceFontSizeRegression } from "./appearance-font-size.electron.mjs"; import { runSettingsMemoryRegression } from "./settings-memory.electron.mjs"; @@ -1188,6 +1189,7 @@ async function main() { browser = await chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`); const page = await waitForAppPage(browser, expoPort); + await installDeveloperToastDismissal(page); const status = await waitForDesktopStatus(page); const checkPluginLinks = () =>