From b371b0aa816b6a2d98cb8f8ded27191954f31be6 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 26 Feb 2026 15:08:34 -0500 Subject: [PATCH 1/2] curr stage --- apps/obsidian/.gitignore | 3 + apps/obsidian/e2e/NOTES.md | 159 ++++++++++++++++++++ apps/obsidian/e2e/helpers/obsidian-setup.ts | 132 ++++++++++++++++ apps/obsidian/e2e/playwright.config.ts | 17 +++ apps/obsidian/e2e/tests/smoke.spec.ts | 123 +++++++++++++++ apps/obsidian/package.json | 5 +- pnpm-lock.yaml | 57 ++++--- 7 files changed, 477 insertions(+), 19 deletions(-) create mode 100644 apps/obsidian/.gitignore create mode 100644 apps/obsidian/e2e/NOTES.md create mode 100644 apps/obsidian/e2e/helpers/obsidian-setup.ts create mode 100644 apps/obsidian/e2e/playwright.config.ts create mode 100644 apps/obsidian/e2e/tests/smoke.spec.ts diff --git a/apps/obsidian/.gitignore b/apps/obsidian/.gitignore new file mode 100644 index 000000000..81613c0af --- /dev/null +++ b/apps/obsidian/.gitignore @@ -0,0 +1,3 @@ +e2e/test-vault/ +e2e/test-results/ +e2e/html-report/ diff --git a/apps/obsidian/e2e/NOTES.md b/apps/obsidian/e2e/NOTES.md new file mode 100644 index 000000000..9dab62b8d --- /dev/null +++ b/apps/obsidian/e2e/NOTES.md @@ -0,0 +1,159 @@ +# E2E Testing for Obsidian Plugin — Notes + +## Approaches Considered + +### Option 1: Playwright `electron.launch()` + +The standard Playwright approach for Electron apps — point `executablePath` at the binary and let Playwright manage the process lifecycle. + +**Pros:** +- First-class Playwright API — `app.evaluate()` runs code in the main process, not just renderer +- Automatic process lifecycle management (launch, close, cleanup) +- Access to Electron-specific APIs (e.g., `app.evaluate(() => process.env)`) +- Well-documented, widely used for Electron testing + +**Cons:** +- **Does not work with Obsidian.** Obsidian's executable is a launcher that loads an `.asar` package (`obsidian-1.11.7.asar`) and forks a new Electron process. Playwright connects to the initial process, which exits, causing `kill EPERM` and connection failures. +- No workaround without modifying Obsidian's startup or using a custom Electron shell + +**Verdict:** Not viable for Obsidian. + +--- + +### Option 2: CDP via `chromium.connectOverCDP()` (chosen) + +Launch Obsidian as a subprocess with `--remote-debugging-port=9222`, then connect via Chrome DevTools Protocol. + +**Pros:** +- Works with Obsidian's forked process architecture — the debug port is inherited by the child process +- Full access to renderer via `page.evaluate()` — Obsidian's global `app` object is available +- Keyboard/mouse interaction works normally +- Can take screenshots, traces, and use all Playwright assertions +- Process is managed explicitly — clear control over startup and teardown + +**Cons:** +- No main process access (can't call Electron APIs directly, only renderer-side `window`/`app`) +- Must manually manage process lifecycle (spawn, pkill, port polling) +- Fixed debug port (9222) means tests can't run in parallel across multiple Obsidian instances without port management +- Port polling adds ~2-5s startup overhead +- `pkill -f Obsidian` in setup is aggressive — kills ALL Obsidian instances, not just test ones + +**Verdict:** Works well for PoC. Sufficient for single-worker CI/local testing. + +--- + +### Option 3: Obsidian's built-in plugin testing (not explored) + +Obsidian has no official testing framework. Some community approaches exist (e.g., `obsidian-jest`, hot-reload-based testing), but none are mature or maintained. + +**Verdict:** Not a real option today. + +--- + +## What We Learned + +### Obsidian internals accessible via `page.evaluate()` +- `app.plugins.plugins["@discourse-graph/obsidian"]` — check plugin loaded +- `app.vault.getMarkdownFiles()` — list files +- `app.vault.read(file)` — read file content +- `app.vault.create(name, content)` — create files +- `app.workspace.openLinkText(path, "", false)` — open a file in the editor +- `app.commands.executeCommandById(id)` — could execute commands directly (alternative to command palette UI) + +### Plugin command IDs +Commands are registered with IDs like `@discourse-graph/obsidian:create-discourse-node`. The command palette shows them as "Discourse Graph: Create discourse node". + +### Modal DOM structure +The `ModifyNodeModal` renders React inside Obsidian's `.modal-container`: +- Node type: `` (`.modal-container input[type='text']`) +- Confirm: `