From df9397af0bf6d8e42f257871dcd8fcb5dc64c732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20Jos=C3=A9=20Ferreira=20J=C3=BAnior?= Date: Sun, 9 Aug 2026 20:10:38 -0300 Subject: [PATCH 1/3] Assert durable state instead of self-dismissing toasts in desktop E2E Base UI toasts dismiss after 5s, so a slow run loses the affordance before the assertion resolves. The on-disk contents, the Save menu item state, and the frontend's operationFailed diagnostic already prove the same crossings of the native boundary and outlive the operation. missing-document-error no longer covers the rendered error text; the errorKind-to-message mapping is unit-tested in documentErrors.test.ts. --- docs/architecture.md | 2 +- e2e/desktop/specs/document-lifecycle.e2e.ts | 3 --- e2e/desktop/specs/missing-document-error.e2e.ts | 8 +++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index e15926f..44e251c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -171,6 +171,6 @@ Automated tests focus on: The Windows-local assembled desktop E2E suite complements those component and boundary tests without replacing them. It runs one embedded WebDriver worker at a time against an isolated debug binary and starts fresh application processes for independent scenarios. The suite retains the Help → Diagnostics smoke path, then adds narrow assembled-boundary assertions for the document lifecycle, real folder-watcher refresh, typed backend error propagation, persisted settings across restart, injected frame controls, and the clean window-close handshake. -User-visible acceptance paths use semantic UI interactions. Direct bridge execution is limited to corroborating diagnostic state, while Node-side filesystem, persisted-store, log, and process access provides deterministic setup or evidence around the native boundary. WebDriver plugins, permissions, and frontend integration remain limited to the dedicated desktop E2E build and are excluded from ordinary application builds. +User-visible acceptance paths use semantic UI interactions. Scenarios decide that an operation happened from state that outlives it — on-disk contents, menu item state, editor contents, or diagnostic records — never from an affordance that dismisses on a timer, such as a toast. Direct bridge execution is limited to corroborating diagnostic state, while Node-side filesystem, persisted-store, log, and process access provides deterministic setup or evidence around the native boundary. WebDriver plugins, permissions, and frontend integration remain limited to the dedicated desktop E2E build and are excluded from ordinary application builds. The manual [Markdown corpus](../corpus/README.md) complements automated tests for parsing, rendering, editing, serialization, folder navigation, and local resources. Keep corpus scenarios aligned with the specification when supported behavior changes; use its README for fixture taxonomy and byte-sensitive handling. diff --git a/e2e/desktop/specs/document-lifecycle.e2e.ts b/e2e/desktop/specs/document-lifecycle.e2e.ts index ddf38cc..a8ede04 100644 --- a/e2e/desktop/specs/document-lifecycle.e2e.ts +++ b/e2e/desktop/specs/document-lifecycle.e2e.ts @@ -30,9 +30,6 @@ describe("desktop document lifecycle", () => { await editor.click(); await browser.keys([Key.Ctrl, "s", Key.NULL]); - await expect($('[data-slot="toast"][data-type="success"]')).toHaveText( - expect.stringContaining("Document saved."), - ); await browser.waitUntil( async () => (await readFile(document.path, "utf8")) === document.savedMarkdown, diff --git a/e2e/desktop/specs/missing-document-error.e2e.ts b/e2e/desktop/specs/missing-document-error.e2e.ts index 0cc876f..3bcce2e 100644 --- a/e2e/desktop/specs/missing-document-error.e2e.ts +++ b/e2e/desktop/specs/missing-document-error.e2e.ts @@ -5,15 +5,11 @@ import { getDesktopE2ERunContext } from "../support/runContext.js"; import { openRecentPath } from "../support/ui.js"; describe("desktop backend error propagation", () => { - it("surfaces a real missing-file IPC error with structured diagnostics", async () => { + it("records a real missing-file IPC error as a structured frontend diagnostic", async () => { const { missingDocumentPath } = await getDesktopE2ERunContext(); await openRecentPath(missingDocumentPath); - const toast = $('[data-slot="toast"][data-type="error"]'); - await expect(toast).toHaveText(expect.stringContaining("Markdown file not found.")); - await expect(toast).toHaveText(expect.stringContaining(missingDocumentPath)); - await waitForDiagnosticRecord( (record) => record.event === "operationFailed" && @@ -22,5 +18,7 @@ describe("desktop backend error propagation", () => { record.errorKind === "missingFile" && record.path === missingDocumentPath, ); + + await expect($('[contenteditable="true"]')).not.toExist(); }); }); From 6501f41e68c9eb7293d4f0b734c95635d635d8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20Jos=C3=A9=20Ferreira=20J=C3=BAnior?= Date: Sun, 9 Aug 2026 20:27:18 -0300 Subject: [PATCH 2/3] Hold toasts open in the desktop E2E build Base UI dismisses toasts after 5s, and a slow run loses the affordance before the assertion resolves. The missing-file scenario reports its outcome through the toast, so the E2E build disables auto-dismissal rather than the scenario dropping that coverage. --- docs/architecture.md | 2 +- e2e/desktop/specs/missing-document-error.e2e.ts | 6 +++++- src/app.tsx | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 44e251c..53eeef2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -171,6 +171,6 @@ Automated tests focus on: The Windows-local assembled desktop E2E suite complements those component and boundary tests without replacing them. It runs one embedded WebDriver worker at a time against an isolated debug binary and starts fresh application processes for independent scenarios. The suite retains the Help → Diagnostics smoke path, then adds narrow assembled-boundary assertions for the document lifecycle, real folder-watcher refresh, typed backend error propagation, persisted settings across restart, injected frame controls, and the clean window-close handshake. -User-visible acceptance paths use semantic UI interactions. Scenarios decide that an operation happened from state that outlives it — on-disk contents, menu item state, editor contents, or diagnostic records — never from an affordance that dismisses on a timer, such as a toast. Direct bridge execution is limited to corroborating diagnostic state, while Node-side filesystem, persisted-store, log, and process access provides deterministic setup or evidence around the native boundary. WebDriver plugins, permissions, and frontend integration remain limited to the dedicated desktop E2E build and are excluded from ordinary application builds. +User-visible acceptance paths use semantic UI interactions. Scenarios decide that an operation happened from state that outlives it — on-disk contents, menu item state, editor contents, or diagnostic records — rather than from an affordance that dismisses on a timer. Where the notification is itself the reported outcome, the desktop E2E build disables toast auto-dismissal so the assertion reads a settled affordance instead of racing it. Direct bridge execution is limited to corroborating diagnostic state, while Node-side filesystem, persisted-store, log, and process access provides deterministic setup or evidence around the native boundary. WebDriver plugins, permissions, and frontend integration remain limited to the dedicated desktop E2E build and are excluded from ordinary application builds. The manual [Markdown corpus](../corpus/README.md) complements automated tests for parsing, rendering, editing, serialization, folder navigation, and local resources. Keep corpus scenarios aligned with the specification when supported behavior changes; use its README for fixture taxonomy and byte-sensitive handling. diff --git a/e2e/desktop/specs/missing-document-error.e2e.ts b/e2e/desktop/specs/missing-document-error.e2e.ts index 3bcce2e..23311a6 100644 --- a/e2e/desktop/specs/missing-document-error.e2e.ts +++ b/e2e/desktop/specs/missing-document-error.e2e.ts @@ -5,11 +5,15 @@ import { getDesktopE2ERunContext } from "../support/runContext.js"; import { openRecentPath } from "../support/ui.js"; describe("desktop backend error propagation", () => { - it("records a real missing-file IPC error as a structured frontend diagnostic", async () => { + it("surfaces a real missing-file IPC error with structured diagnostics", async () => { const { missingDocumentPath } = await getDesktopE2ERunContext(); await openRecentPath(missingDocumentPath); + const toast = $('[data-slot="toast"][data-type="error"]'); + await expect(toast).toHaveText(expect.stringContaining("Markdown file not found.")); + await expect(toast).toHaveText(expect.stringContaining(missingDocumentPath)); + await waitForDiagnosticRecord( (record) => record.event === "operationFailed" && diff --git a/src/app.tsx b/src/app.tsx index cbd0a4c..4088835 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -26,6 +26,8 @@ const DeveloperTools = import.meta.env.DEV }) : null; +const TOAST_TIMEOUT_MS = import.meta.env.MODE === "desktop-e2e" ? 0 : undefined; + const setDarkAppearance = (isDark: boolean) => { window.document.documentElement.classList.toggle("dark", isDark); }; @@ -138,7 +140,7 @@ export function App() { /> )} - + ); } From 168b385e2b9d604bfb1220a74237ee69f4b12f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20Jos=C3=A9=20Ferreira=20J=C3=BAnior?= Date: Mon, 10 Aug 2026 01:33:47 -0300 Subject: [PATCH 3/3] Note the E2E coupling at the toast creation site --- src/lib/toast.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/toast.ts b/src/lib/toast.ts index 0686f80..5f1efe4 100644 --- a/src/lib/toast.ts +++ b/src/lib/toast.ts @@ -30,6 +30,7 @@ const showToast = ( const message: MessageData = typeof messageOrTitle === "string" ? { title: messageOrTitle, description } : messageOrTitle; + // A per-toast timeout would override the provider default that holds toasts open in the E2E build. toastManager.add({ description: message.description, title: message.title,