test(e2e): keep developer toasts out of concurrent app assertions

Concurrent layout measurements could invoke the same Playwright locator handler before its first dismissal finished, leaving checks waiting for a removed button. Hide only the Expo toast host through test styling while retaining console error capture.
This commit is contained in:
Mohamed Boudra
2026-10-20 08:37:57 +02:00
parent 398c1a3b52
commit 62a5eac186
4 changed files with 45 additions and 11 deletions
@@ -1,4 +1,5 @@
import { expect, test } from "../support/fixtures";
import type { Page } from "@playwright/test";
import { openAddProjectFlow, addProjectFlow } from "../support/helpers/add-project-flow";
import { installUsageReportsFixture } from "../support/helpers/usage-reports";
import {
@@ -31,6 +32,28 @@ import {
setSidebarNavItemVisible,
} from "../support/helpers/sidebar-nav-settings";
async function showDeveloperToast(page: Page): Promise<void> {
await page.evaluate(() => {
document.getElementById("error-toast")?.remove();
const toast = document.createElement("div");
toast.id = "error-toast";
toast.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:white";
// Expo renders its unnamed dismiss button in an open shadow root.
const button = document.createElement("button");
button.style.cssText = "width:24px;height:24px";
button.addEventListener("click", () => toast.remove());
toast.attachShadow({ mode: "open" }).appendChild(button);
document.body.appendChild(toast);
});
}
test("developer toasts leave concurrent sidebar layout checks uninterrupted", async ({ page }) => {
await gotoAppShell(page);
await showDeveloperToast(page);
await expectSidebarOrder(page, ["new-workspace", "history", "search", "schedules"]);
await expect(page.locator("#error-toast")).toBeHidden();
});
test("fixed footer line keeps its five icons, Help and Settings at the end", async ({ page }) => {
test.setTimeout(120_000);
await installUsageReportsFixture(page, { lists: [() => claudeAndCodexReports()] });
+18 -7
View File
@@ -1,10 +1,21 @@
/** @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.
export async function hideDeveloperToasts(page) {
// Keep developer chrome out of app interactions without Playwright locator
// handlers: parallel app assertions can race while dismissing the same toast.
// 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,
});
function hideToasts() {
const installStyle = () => {
const style = document.createElement("style");
style.textContent = "#error-toast { display: none !important; }";
document.head.appendChild(style);
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", installStyle, { once: true });
} else {
installStyle();
}
}
await page.addInitScript(hideToasts);
// Electron connects to an already loaded page; apply there as well.
await page.evaluate(hideToasts);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { test as base, expect, type Page } from "@playwright/test";
import { installDeveloperToastDismissal } from "./developer-ui.mjs";
import { hideDeveloperToasts } from "./developer-ui.mjs";
import { startOutdatedDaemon, type OutdatedDaemon } from "./helpers/daemon-update";
import { startE2EWorker } from "./helpers/e2e-worker";
import { getE2EDaemonPort } from "./helpers/daemon-port";
@@ -131,7 +131,7 @@ const test = daemonTest.extend<{
await ws.close({ code: 1008, reason: "Blocked connection to localhost:6767 during e2e." });
});
await installDeveloperToastDismissal(page);
await hideDeveloperToasts(page);
const entries: string[] = [];
+2 -2
View File
@@ -12,7 +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 { hideDeveloperToasts } from "../../app/e2e/support/developer-ui.mjs";
import { runAppearanceFontSizeRegression } from "./appearance-font-size.electron.mjs";
import { runSettingsMemoryRegression } from "./settings-memory.electron.mjs";
@@ -1189,7 +1189,7 @@ async function main() {
browser = await chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`);
const page = await waitForAppPage(browser, expoPort);
await installDeveloperToastDismissal(page);
await hideDeveloperToasts(page);
const status = await waitForDesktopStatus(page);
const checkPluginLinks = () =>