From 03886b92e1ce3a5c29b2166d24ca4338fa216c98 Mon Sep 17 00:00:00 2001 From: David Veszelovszki Date: Tue, 19 May 2026 00:12:50 +0200 Subject: [PATCH] Playwright E2E: stamp running test name into window title beforeEach sets the main window's OS title to ` (Running: )` and afterEach appends `(FINISHED)`, so the dock / Cmd-Tab / Linux title bar tells you which spec is in flight without tailing the log. Base title is captured once per worker so suffixes don't accumulate across tests. Uses the standard `plugin:window|set_title` Tauri command; permissions are added only to the feature-gated `playwright.json` capability that `build.rs` generates when `playwright-e2e` is on, so release builds are unaffected. --- apps/desktop/src-tauri/build.rs | 6 ++- apps/desktop/test/e2e-playwright/CLAUDE.md | 9 ++++ apps/desktop/test/e2e-playwright/fixtures.ts | 51 ++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs index 88bfc62a..74a1c28e 100644 --- a/apps/desktop/src-tauri/build.rs +++ b/apps/desktop/src-tauri/build.rs @@ -24,7 +24,11 @@ fn main() { "identifier": "playwright", "description": "Playwright E2E testing plugin permissions (auto-generated by build.rs)", "windows": ["main", "settings", "viewer-*"], - "permissions": ["playwright:default"] + "permissions": [ + "playwright:default", + "core:window:allow-title", + "core:window:allow-set-title" + ] } "#, ) diff --git a/apps/desktop/test/e2e-playwright/CLAUDE.md b/apps/desktop/test/e2e-playwright/CLAUDE.md index e79bcc30..664fbd40 100644 --- a/apps/desktop/test/e2e-playwright/CLAUDE.md +++ b/apps/desktop/test/e2e-playwright/CLAUDE.md @@ -16,6 +16,15 @@ Playwright (Node.js) --Unix socket--> tauri-plugin-playwright (Rust) Same tests run on macOS and Linux. Platform differences (Ctrl vs Meta) are handled by a single `CTRL_OR_META` constant in `helpers.ts`. +## Window-title decoration + +`fixtures.ts` wraps each test in `beforeEach`/`afterEach` hooks that update the main window's OS title via the standard +Tauri window plugin (`plugin:window|set_title`). While a test runs the title becomes ` (Running: )`; after +it returns, `(FINISHED)` is appended. The base title is captured once per worker on the first hook call so suffixes +don't accumulate. The required permissions (`core:window:allow-title`, `core:window:allow-set-title`) are added to the +feature-gated `playwright.json` capability generated by `build.rs`, so they're absent from release builds. Use this in +Cmd-Tab / Mission Control / Linux title bars to spot which spec is in flight or stuck without tailing the log. + ## Running on macOS **Via the checker (recommended):** The checker handles the full lifecycle automatically: build, fixture creation, app diff --git a/apps/desktop/test/e2e-playwright/fixtures.ts b/apps/desktop/test/e2e-playwright/fixtures.ts index 030d4cd3..fde8159b 100644 --- a/apps/desktop/test/e2e-playwright/fixtures.ts +++ b/apps/desktop/test/e2e-playwright/fixtures.ts @@ -9,9 +9,16 @@ * - globalSetup: creates the fixture directory tree (~170 MB) * - beforeEach: recreates small text files (keeps bulk .dat files) * - globalTeardown: deletes the fixture directory + * + * Window-title decoration: + * - `beforeEach` sets the main window's OS title to " (Running: )" + * - `afterEach` updates it to " (Running: ) (FINISHED)" + * so you can glance at the dock / Cmd-Tab / Linux title bar to see which + * spec is in flight (or stuck) without tailing the log. */ import { createTauriTest } from '@srsholmes/tauri-playwright' +import type { TestInfo } from '@playwright/test' // Each parallel E2E shard spawns its own Tauri instance bound to a distinct // Unix socket. The Go check runner sets CMDR_PLAYWRIGHT_SOCKET per shard. @@ -26,3 +33,47 @@ export const { test, expect } = createTauriTest({ // Tauri mode config mcpSocket: socketPath, }) + +// Captured once per worker on the first beforeEach so suffixes don't accumulate +// across tests. Each shard owns its own Tauri instance + its own worker process, +// so this lives correctly per-shard. +let baseTitle: string | null = null + +type EvaluatablePage = { evaluate: (js: string) => Promise } + +/** Joins describe blocks + test title into "Section > test name" style. */ +function formatTestName(info: TestInfo): string { + const parts = info.titlePath + const fileIdx = parts.findIndex((p) => /\.spec\.[tj]s$/.test(p)) + const tail = fileIdx >= 0 ? parts.slice(fileIdx + 1) : [info.title] + return tail.filter((p) => p.length > 0).join(' › ') +} + +async function readMainTitle(tauriPage: EvaluatablePage): Promise { + const result = await tauriPage.evaluate(`window.__TAURI_INTERNALS__.invoke('plugin:window|title', { label: 'main' })`) + return typeof result === 'string' ? result : '' +} + +async function setMainTitle(tauriPage: EvaluatablePage, title: string): Promise { + await tauriPage.evaluate( + `window.__TAURI_INTERNALS__.invoke('plugin:window|set_title', { label: 'main', value: ${JSON.stringify(title)} })`, + ) +} + +test.beforeEach(async ({ tauriPage }, testInfo) => { + try { + if (baseTitle === null) baseTitle = await readMainTitle(tauriPage) + await setMainTitle(tauriPage, `${baseTitle} (Running: ${formatTestName(testInfo)})`) + } catch { + // Title decoration is purely for human eyeballs — never block a test on it. + } +}) + +test.afterEach(async ({ tauriPage }, testInfo) => { + try { + if (baseTitle === null) baseTitle = await readMainTitle(tauriPage) + await setMainTitle(tauriPage, `${baseTitle} (Running: ${formatTestName(testInfo)}) (FINISHED)`) + } catch { + // See beforeEach. + } +})