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
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -86,3 +87,67 @@ jobs:

- name: Audit Rust dependencies
run: cargo audit --file src-tauri/Cargo.lock

test-e2e-desktop:
name: Desktop E2E
runs-on: windows-2025-vs2026
timeout-minutes: 20
env:
CARGO_PROFILE_DEV_DEBUG: 0

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: lts/*
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Setup Rust
run: rustup toolchain install

- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target/desktop-e2e"
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build desktop E2E binary
run: pnpm build:e2e:desktop

- name: Run desktop E2E suite
run: pnpm test:e2e:desktop:run

- name: Upload failure evidence
if: failure()
uses: actions/upload-artifact@v7
with:
name: desktop-e2e-artifacts
path: e2e/desktop/artifacts/
retention-days: 7

- name: Assert clean teardown
if: always()
shell: pwsh
run: |
# WebView2 host processes outlive the suite by a few seconds; they are
# not asserted on, but give them time to exit before sampling.
Start-Sleep -Seconds 10

$orphans = Get-Process -Name leafdown-e2e, msedgedriver -ErrorAction SilentlyContinue
$listeners = Get-NetTCPConnection -LocalPort 4445 -State Listen -ErrorAction SilentlyContinue

if ($orphans -or $listeners) {
$orphans | Format-Table -AutoSize Name, Id, StartTime
$listeners | Format-Table -AutoSize LocalAddress, LocalPort, OwningProcess
throw "Desktop E2E left an orphaned process or a listener on port 4445."
}
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ Verify a fresh development environment with:
pnpm check
```

Run the Windows-local assembled desktop E2E suite with:
Run the assembled desktop E2E suite, which requires Windows, with:

```powershell
pnpm test:e2e:desktop
```

This explicit suite is not part of `pnpm check`. It builds an isolated debug binary with test-only WebDriver capabilities, then runs one embedded WebDriver worker at a time on port 4445 across fresh application sessions. The embedded provider does not require an external WebDriver. Keep port 4445 available while it runs. The test identifier, persisted store, and target directory are separate from ordinary Leafdown builds.
This explicit suite is not part of `pnpm check`; CI runs it as its own job on every pull request and every push to `main`, so it is enforced without changing what you run locally. It builds an isolated debug binary with test-only WebDriver capabilities, then runs one embedded WebDriver worker at a time on port 4445 across fresh application sessions. The embedded provider does not require an external WebDriver. Keep port 4445 available while it runs. The test identifier, persisted store, and target directory are separate from ordinary Leafdown builds.

The runner resets only the isolated E2E persisted store, leaving the application to write its own defaults, creates temporary filesystem fixtures, and removes both after the suite. Each run writes ignored runner, frontend, backend, and focused diagnostic evidence under `e2e/desktop/artifacts/<run>/<scenario>/`. A failed test also captures a screenshot, the real diagnostics summary, the test error, and a semantic UI snapshot that excludes editor content. A failed run additionally writes `fixture-manifest.json` under `e2e/desktop/artifacts/<run>/`, recording each temporary fixture's path, expected and actual hash and size, and modification time before cleanup removes it. These artifacts are retained until manually deleted. Treat them as potentially sensitive because diagnostics and errors may contain local paths.
The runner resets only the isolated E2E persisted store, leaving the application to write its own defaults, creates temporary filesystem fixtures, and removes both after the suite. Each run writes ignored runner, frontend, backend, and focused diagnostic evidence under `e2e/desktop/artifacts/<run>/<scenario>/`. A failed test also captures a screenshot, the real diagnostics summary, the test error, and a semantic UI snapshot that excludes editor content. A failed run additionally writes `fixture-manifest.json` under `e2e/desktop/artifacts/<run>/`, recording each temporary fixture's path, expected and actual hash and size, and modification time before cleanup removes it. Local artifacts are retained until manually deleted. Treat them as potentially sensitive because diagnostics and errors may contain local paths. CI uploads the same evidence only when the job fails, retained for seven days; those artifacts contain runner paths rather than a contributor's.

To verify the failure-evidence path, run the suite with the forced-failure flag:

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Automated tests focus on:
- Literal HTML rendering and script-execution prevention.
- Context popup layout and caret-based marker visibility.

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.
The assembled desktop E2E suite complements those component and boundary tests without replacing them. It requires Windows and stays outside `pnpm check`, running through an explicit command locally and as its own CI job on every pull request and push to `main`. 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 — 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.

Expand Down
2 changes: 1 addition & 1 deletion e2e/desktop/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const waitForPortRelease = async (timeoutMs = 10_000) => {

const main = async () => {
if (!process.env.APPDATA) {
throw new Error("APPDATA is required for the Windows-local desktop E2E suite.");
throw new Error("APPDATA is required for the Windows desktop E2E suite.");
}

await mkdir(artifactsRoot, { recursive: true });
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"build:frontend": "tsc -b && vite build",
"build:frontend:e2e": "tsc -b && vite build --mode desktop-e2e",
"build:e2e:desktop": "tauri build --debug --no-bundle --features desktop-e2e --config e2e/desktop/tauri.conf.json -- --target-dir target/desktop-e2e",
"test:e2e:desktop": "pnpm build:e2e:desktop && tsx e2e/desktop/run.ts",
"test:e2e:desktop": "pnpm build:e2e:desktop && pnpm test:e2e:desktop:run",
"test:e2e:desktop:run": "tsx e2e/desktop/run.ts",
"preview": "vite preview",
"tauri": "tauri",
"lint": "pnpm lint:frontend && pnpm lint:backend",
Expand Down