Skip to content

ENG-1583: Automate Obsidian dev environment e2e testing#959

Open
trangdoan982 wants to merge 4 commits into
mainfrom
worktree-eng-1583-automate-obsidian-dev-environment-load-and-visual-validation
Open

ENG-1583: Automate Obsidian dev environment e2e testing#959
trangdoan982 wants to merge 4 commits into
mainfrom
worktree-eng-1583-automate-obsidian-dev-environment-load-and-visual-validation

Conversation

@trangdoan982

@trangdoan982 trangdoan982 commented Apr 13, 2026

Copy link
Copy Markdown
Member

https://www.loom.com/share/7614e7da36184f109dd867621f68aa97

Summary

Adds a Playwright-based end-to-end test harness for the Obsidian plugin. Because Obsidian forks Electron on launch, tests connect via Chrome DevTools Protocol (CDP) instead of Playwright's native Electron API. The suite is organized in layers — launch/setup helpers, reusable scenarios, and a thin smoke spec — so agents and developers can run pnpm test:e2e after changes and get deterministic feedback.


Architecture

Why CDP?

Obsidian's macOS binary is a launcher that forks a child Electron process. Playwright's electron.launch() attaches to the launcher, which exits immediately. CDP avoids that: launch Obsidian with --remote-debugging-port=9222, connect with chromium.connectOverCDP(), and drive the renderer where Obsidian's global app object lives.

The Obsidian CLI controls a running instance; it cannot start one with a debug port. CDP launch is required; CLI is optional afterward.

Layered layout

apps/obsidian/e2e/
├── constants.ts              # Plugin ID, command IDs, palette labels (from manifest.json)
├── fixtures/obsidian.ts      # Worker-scoped lifecycle: vault → launch → teardown
├── helpers/
│   ├── obsidian-setup.ts     # Vault prep, obsidian.json, CDP launch, page discovery
│   ├── commands.ts           # executeCommand / executeCommandViaPalette
│   ├── modal.ts              # ModifyNodeModal interactions
│   ├── vault.ts              # In-app vault reads, plugin registry waits
│   └── screenshots.ts        # Step screenshots for visual validation
├── scenarios/
│   ├── plugin-load.ts        # Assert plugin registers in app.plugins
│   └── node-creation.ts      # Create a Question node (command API or palette)
├── tests/smoke.spec.ts       # Thin orchestrator — wires scenarios together
└── playwright.config.ts      # workers: 1, loads apps/obsidian/.env

Fixtures own process lifecycle. Helpers are reusable primitives. Scenarios hold assertions and user flows. Specs only call scenarios — no duplicated test logic.

Launch and teardown flow

sequenceDiagram
  participant Fixture as Worker fixture
  participant Setup as obsidian-setup
  participant Obsidian
  participant PW as Playwright CDP
  participant Test as smoke.spec

  Fixture->>Setup: resolveVaultPath()
  alt default vault
    Fixture->>Setup: createTestVault() — wipe + install plugin
  else OBSIDIAN_TEST_VAULT set
    Fixture->>Setup: ensureVaultWithPlugin() — non-destructive
  end
  Setup->>Setup: killObsidianOnDebugPort(9222)
  Setup->>Setup: setActiveVault() in obsidian.json
  Setup->>Obsidian: open -na Obsidian.app --args --remote-debugging-port=9222
  Setup->>PW: connectOverCDP + findWorkspacePage()
  Setup->>Setup: verifyActiveVault(basePath)
  Fixture->>Test: obsidian.page
  Test->>Test: run scenarios via page.evaluate(app.*)
  Fixture->>Setup: cleanup scratch files, browser.close()
  Setup->>Setup: killObsidianOnDebugPort(), restore obsidian.json
  alt default vault
    Setup->>Setup: cleanTestVault()
  end
Loading

Vault selection: Obsidian has no --vault flag. Tests set "open": true on the target vault in ~/Library/Application Support/obsidian/obsidian.json before launch, then restore the original config after the run.

Workspace page discovery: Obsidian opens multiple CDP targets (dev console, workspace). findWorkspacePage() scans for the page with a visible .workspace element.

Single instance: workers: 1 and a worker-scoped fixture share one Obsidian process per run. Port 9222 is fixed, so parallel workers would conflict.

How tests interact with Obsidian

Tests run code inside Obsidian's renderer via page.evaluate():

Capability API
Plugin loaded app.plugins.plugins[PLUGIN_ID]
Run command app.commands.executeCommandById(...)
List/read vault files app.vault.getMarkdownFiles(), app.vault.read()
Active vault path app.vault.adapter.basePath

Command triggering has two paths:

  • Command API (default in scenarios) — fast, reliable; use for most coverage
  • Command palette — exercises real UI typing; reserved for palette-specific tests

Node creation goes through ModifyNodeModal (.modal-container textarea, not <input>), then asserts the vault contains a file with the expected QUE - {content} basename and nodeTypeId frontmatter.

Configuration

Copy e2e/.env.exampleapps/obsidian/.env (loaded by playwright.config.ts):

Variable Purpose
OBSIDIAN_APP_PATH Path to Obsidian executable (default: /Applications/Obsidian.app/Contents/MacOS/Obsidian)
OBSIDIAN_TEST_VAULT Optional vault override; skips destructive vault cleanup on teardown

Prerequisites: pnpm build (copies dist/ into the test vault), Obsidian CLI enabled (Settings → General), quit other Obsidian instances before running tests.

Smoke suite

pnpm test:e2e runs smoke.spec.ts:

  1. Plugin loads — waits for plugin registration, captures screenshot
  2. Creates a Question node via command palette — full modal flow + vault assertion

The node-creation scenario also supports trigger: "command" for the faster API path; add a smoke test when you want that covered in CI.

Step screenshots land in e2e/test-results/<test-name>/ for agent/human visual validation.


Test plan

  • Set OBSIDIAN_TEST_VAULT in apps/obsidian/.env to an existing vault
  • Run pnpm build in apps/obsidian
  • Run pnpm test:e2e — Obsidian launches the correct vault, plugin loads, node creation works
  • Run without OBSIDIAN_TEST_VAULT — temp vault is created and cleaned up
  • Confirm obsidian.json is restored after tests

Out of scope (follow-ups)

  • CI on macOS runners (Obsidian needs a GUI; no headless mode)
  • Per-run temp dirs and random debug ports for parallel workers
  • Expanded scenario coverage (all node types, settings panel, discourse context)
  • Visual regression baselines

Design notes and phased roadmap: apps/obsidian/e2e/NOTES.md.

@linear-code

linear-code Bot commented Apr 13, 2026

Copy link
Copy Markdown

@supabase

supabase Bot commented Apr 13, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/obsidian/src/utils/syncDgNodesToSupabase.ts Outdated
@trangdoan982 trangdoan982 force-pushed the worktree-eng-1583-automate-obsidian-dev-environment-load-and-visual-validation branch from 0d44589 to 1cb7a28 Compare April 13, 2026 15:49
devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 force-pushed the worktree-eng-1583-automate-obsidian-dev-environment-load-and-visual-validation branch from 1cb7a28 to c9f522a Compare April 13, 2026 20:06

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/obsidian/e2e/tests/smoke.spec.ts Outdated
Comment thread apps/obsidian/e2e/helpers/screenshots.ts Outdated
Comment thread apps/obsidian/e2e/helpers/obsidian-setup.ts Outdated
Comment thread apps/obsidian/e2e/helpers/vault.ts Outdated
trangdoan982 and others added 3 commits June 8, 2026 10:48
Extract inline Obsidian interaction logic into reusable helpers (commands,
vault, modal, screenshots) and split smoke.spec.ts into focused spec files
for plugin-load and node-creation. Add JSON reporter to playwright config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix vault selection by manipulating obsidian.json instead of unsupported --vault flag
- Find correct workspace page via DOM query instead of assuming pages()[0]
- Load .env via dotenv for OBSIDIAN_TEST_VAULT configuration
- Replace blind waitForTimeout calls with proper selector/function waits
- Add ensureVaultWithPlugin for safe setup on existing vaults
- Add CDP connection retry logic
- Scope eslint-disables to individual page.evaluate blocks
- Use crypto.randomBytes for proper hex vault IDs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update pnpm-lock.yaml for @playwright/test, use port-based Obsidian teardown in afterAll, and switch multi-arg e2e helpers to named parameters.

Co-authored-by: Cursor <cursoragent@cursor.com>
@trangdoan982 trangdoan982 force-pushed the worktree-eng-1583-automate-obsidian-dev-environment-load-and-visual-validation branch from c9f522a to 21b7625 Compare June 8, 2026 14:52
Comment thread apps/obsidian/e2e/helpers/modal.ts Outdated
@trangdoan982 trangdoan982 requested a review from mdroidian June 9, 2026 21:30
@mdroidian

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60025b975b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

createTestVault(vaultPath);
}

const launched = await launchObsidian(vaultPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore Obsidian state when launch setup fails

When launchObsidian() throws before use is reached (for example the debug port never opens or verifyActiveVault fails after setActiveVault has rewritten ~/Library/Application Support/obsidian/obsidian.json), Playwright never executes the teardown code below, so the user's Obsidian config remains pointed at the test vault and any launched process may keep running. Wrap the launch/setup path in try/finally (or make launchObsidian roll back on failure) so cleanup happens even when fixture setup fails.

Useful? React with 👍 / 👎.

Comment on lines +60 to +61
(f: { path: string; basename: string }) =>
f.path === scratchPath || f.basename.startsWith(legacyPrefix),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delete the nodes created in custom vault runs

When OBSIDIAN_TEST_VAULT is set, teardown intentionally skips cleanTestVault, but this cleanup only removes e2e-scratch.md and legacy scratch-e2e-* files. The smoke scenario creates QUE - What is discourse graph testing ... notes in that same user-provided vault, so every e2e run leaves test nodes behind; track/delete the created basename or give e2e-created nodes a cleanup prefix that this filter removes.

Useful? React with 👍 / 👎.

@mdroidian mdroidian left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits below from me, codex comments worth looking into

But the blocking change request is: clearer documentation/instructions on how to it. Right now NOTES.md is a combination of a conversation/decisions made with the LLM, but it has a lot of fluff/extra unnecessary noise. Let's drop the "options" and "phases" etc (things like that should live in linear) and only add instructions for someone who wants to use this feature.

@@ -0,0 +1,240 @@
# E2E Testing for Obsidian Plugin — Notes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an artifact from an LLM conversation. Should it live in the repo? If so, why?

@@ -0,0 +1,19 @@
import type { Page } from "@playwright/test";
import { PLUGIN_ID } from "../constants";
import { captureStep } from "../helpers/screenshots";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to remember to use our convention of ~ instead of ..

@@ -0,0 +1,25 @@
import path from "path";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to match file naming convention of fileName.ts instead of file-name.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants