Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
"#,
)
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/test/e2e-playwright/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<base> (Running: <name>)`; 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
Expand Down
51 changes: 51 additions & 0 deletions apps/desktop/test/e2e-playwright/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<base> (Running: <test>)"
* - `afterEach` updates it to "<base> (Running: <test>) (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.
Expand All @@ -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<unknown> }

/** 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<string> {
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<void> {
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.
}
})
Loading