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
178 changes: 178 additions & 0 deletions packages/cli/E2E_TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# E2E Tests — @endorhq/capsule (CLI)

The CLI has three subcommands (`share`, `export`, `serve`), a format-aware anonymization engine, and session discovery across four agent log directories. Tests shell out to the built CLI binary or import command functions directly.

---

## Suite: Export

### Preconditions

- Built CLI binary available or commands importable via tsx
- Temp output directory created

### Features

- Export writes anonymized session to specified path
<!-- category: core -->
Run `capsule export <fixture>` with all anonymization options disabled, verify the output file is written and contains valid content matching the original format.

- Export with all anonymization options produces sanitized output
<!-- category: core -->
Run export with all 7 anonymization options enabled on a Claude fixture. Verify: no tool outputs, no file contents, no thinking blocks, no system messages, no token usage, file paths are masked, git info is masked.

- Export preserves JSON/JSONL format integrity
<!-- category: core -->
Export each fixture format. Verify JSONL outputs have one valid JSON object per line; Gemini JSON output is valid JSON.

- Export of empty or minimal session
<!-- category: edge -->
Export a fixture with only a single user message. Verify it produces a valid output file (not empty, not corrupted).

- Export to non-existent directory fails gracefully
<!-- category: error -->
Attempt export to `/nonexistent/path/output.jsonl`. Verify it exits with a non-zero code and prints an error message.

- Export idempotency
<!-- category: idempotency -->
Run the same export twice to different output paths. Verify both outputs are byte-identical.

---

## Suite: Share

### Preconditions

- Mock `gh` CLI script in `$PATH` that simulates auth success and gist creation
- Temp directory for mock script

### Features

- Share publishes session and returns viewer URL
<!-- category: core -->
<!-- skip: requires-real-gist -->
Run `capsule share <fixture>` with mocked `gh`. Verify it calls `gh gist create`, captures the gist ID, and prints a viewer URL matching `https://capsule.endor.dev?gist=<id>`.

- Share checks gh auth before proceeding
<!-- category: core -->
Mock `gh auth status` to exit with code 1. Run `capsule share <fixture>`. Verify it exits early with an auth error message before prompting anything.

- Share fails when gh is not installed
<!-- category: error -->
Remove `gh` from `$PATH`. Run `capsule share <fixture>`. Verify it prints an error about missing `gh` CLI.

- Share with anonymization applies transforms before publishing
<!-- category: core -->
<!-- skip: requires-gh-auth -->
Run share with path masking enabled. Capture the temp file written for gist creation. Verify file paths are masked in the content.

---

## Suite: Serve

### Preconditions

- Web package is built (`pnpm build`)
- Port 0 for random assignment

### Features

- Serve starts HTTP server on default port
<!-- category: core -->
Start `capsule serve` and verify it binds to port 3123 and responds with 200 to `GET /`.

- Serve accepts custom port via --port flag
<!-- category: core -->
Start `capsule serve --port 0`, capture the assigned port from stdout, and verify an HTTP request to that port returns 200.

- Serve handles graceful shutdown on SIGINT
<!-- category: core -->
Start serve, send SIGINT, verify the process exits cleanly (code 0) and the port is released.

- Serve fails with clear error if web package is not built
<!-- category: error -->
Remove or rename the web build output. Run `capsule serve`. Verify it prints an error about missing handler.

- Serve responds with static assets
<!-- category: core -->
Start serve and request a known static asset path (e.g., CSS or JS file). Verify it returns 200 with appropriate content-type.

---

## Suite: Anonymization

### Preconditions

- Sample fixtures loaded for each format

### Features

- Claude: mask file paths replaces real paths with generic ones
<!-- category: core -->
Anonymize a Claude fixture with "mask file paths" enabled. Verify all original paths in tool_use arguments and text blocks are replaced with `/project/src/file{N}{ext}` patterns.

- Claude: remove thinking blocks strips thinking content
<!-- category: core -->
Anonymize a Claude fixture with thinking blocks. Verify the output contains no thinking-type content blocks.

- Claude: remove tool outputs strips tool_result content
<!-- category: core -->
Anonymize a Claude fixture with tool calls. Verify all tool_result entries have their content removed or replaced with a placeholder.

- Codex: remove reasoning items filters reasoning entries
<!-- category: core -->
Anonymize a Codex fixture with reasoning. Verify no reasoning-type items remain in the output.

- Copilot: mask session context sanitizes cwd, branch, repository
<!-- category: core -->
Anonymize a Copilot fixture. Verify the session context fields (cwd, gitBranch, repository) are masked.

- Gemini: anonymization produces valid JSON
<!-- category: core -->
Anonymize a Gemini fixture with all options. Verify the output is valid, pretty-printed JSON with the expected structure.

- Path masker produces consistent mappings
<!-- category: edge -->
Anonymize a fixture where the same path appears multiple times. Verify every occurrence maps to the same masked path.

- Git masker handles branch names and repo URLs
<!-- category: edge -->
Anonymize a fixture containing git branch names and repository URLs. Verify branches become `branch-{N}` and repo URLs become generic.

---

## Suite: Discovery

### Preconditions

- Temp directory simulating `$HOME` with mock agent log directories

### Features

- Discovers Claude sessions from ~/.claude/projects/
<!-- category: core -->
Create a temp dir with `<home>/.claude/projects/<project>/session.jsonl` containing valid Claude log lines. Run discovery. Verify it finds the session with correct metadata (title, date, cwd).

- Discovers Codex sessions from ~/.codex/sessions/
<!-- category: core -->
Create a temp dir with valid Codex session structure. Run discovery. Verify session is found with metadata extracted from `session_meta`.

- Discovers Copilot sessions from ~/.copilot/session-state/
<!-- category: core -->
Create a temp dir with valid Copilot `events.jsonl`. Run discovery. Verify session is found with sessionId from `session.start` event.

- Discovers Gemini sessions from ~/.gemini/tmp/
<!-- category: core -->
Create a temp dir with valid `session-*.json` file. Run discovery. Verify session is found with metadata from JSON content.

- Discovery returns empty list for missing directories
<!-- category: edge -->
Point discovery at a temp `$HOME` with no agent directories. Verify it returns empty arrays without errors.

- Discovery skips malformed session files
<!-- category: error -->
Create agent directories with corrupted/empty files. Verify discovery skips them gracefully and returns only valid sessions.

- Discovery sorts sessions by date descending
<!-- category: side-effect -->
Create multiple sessions with different dates. Verify the returned list is ordered newest-first.
131 changes: 131 additions & 0 deletions packages/web/E2E_TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# E2E Tests — @endorhq/capsule-web

The web app is a SvelteKit single-page application that lets users upload, parse, and visualize AI agent session logs. Tests exercise the full browser workflow: upload a file → parse → render timeline → interact with filters.

Web E2E tests can use either Playwright (full browser) or vitest with @testing-library/svelte for component-level E2E. The choice depends on what the team prefers for this project.

---

## Suite: Upload & Parse

### Preconditions

- Dev server or preview server running
- Test page loaded

### Features

- Upload Claude JSONL file and render timeline
<!-- category: core -->
Upload `claude-simple.jsonl`. Verify: session appears in sidebar, timeline renders with user and assistant messages, format shows "Claude".

- Upload Codex JSONL file and render timeline
<!-- category: core -->
Upload `codex-simple.jsonl`. Verify: session appears in sidebar, timeline renders correctly, format shows "Codex".

- Upload Copilot JSONL file and render timeline
<!-- category: core -->
Upload `copilot-simple.jsonl`. Verify similar to above with "Copilot".

- Upload Gemini JSON file and render timeline
<!-- category: core -->
Upload `gemini-simple.json`. Verify similar to above with "Gemini".

- Format auto-detection identifies correct agent
<!-- category: core -->
Upload each fixture without specifying format. Verify `detectFormat()` assigns the correct `AgentFormat` for each file.

- Upload file with tool calls renders nested tool blocks
<!-- category: core -->
Upload `claude-with-tools.jsonl`. Verify tool call entries appear in the timeline with name, arguments, and matched results.

- Upload file with thinking blocks renders thinking sections
<!-- category: core -->
Upload `claude-with-thinking.jsonl`. Verify thinking blocks appear attached to assistant messages.

- Upload invalid file shows error
<!-- category: error -->
Upload a file with random text content. Verify an error message is displayed and no session is added to the sidebar.

- Upload very large file does not hang
<!-- category: edge -->
Upload a fixture with 1000+ lines. Verify parsing completes within a reasonable time and the timeline renders.

---

## Suite: Gist Loading

### Preconditions

- Mock fetch or MSW intercepting GitHub Gist API requests

### Features

- Load session from gist ID via URL parameter
<!-- category: core -->
Navigate to `/?gist=<mock-id>`. Verify the gist content is fetched, parsed, and displayed in the timeline.

- Gist loading shows error for invalid gist ID
<!-- category: error -->
Navigate to `/?gist=nonexistent`. Verify an error message is displayed.

- Gist loading with real GitHub API
<!-- category: core -->
<!-- skip: requires-real-gist -->
Load a known public gist. Verify content renders correctly.

---

## Suite: Storage

### Preconditions

- Storage initialized (OPFS, IndexedDB, or memory fallback)

### Features

- Uploaded session persists across page reloads
<!-- category: core -->
Upload a session, reload the page, verify the session still appears in the sidebar and can be selected.

- Multiple sessions can be stored and switched between
<!-- category: core -->
Upload two different sessions. Click each in the sidebar. Verify the timeline updates to show the selected session's content.

- Clear all sessions removes everything
<!-- category: side-effect -->
Upload sessions, clear all, verify sidebar is empty and storage is cleaned up.

- Storage fallback works when OPFS is unavailable
<!-- category: edge -->
Disable OPFS (e.g., in a worker-less context). Verify storage falls back to IndexedDB or memory and sessions still work.

- Re-uploading same file does not create duplicates
<!-- category: idempotency -->
Upload the same file twice. Verify only one session entry appears (or two entries if that's the intended behavior — adjust based on actual implementation).

---

## Suite: Session Viewer

### Preconditions

- A session is uploaded and selected

### Features

- Filter bar filters timeline entries by type
<!-- category: core -->
Upload a session with mixed entry types. Use the filter bar to hide tool calls. Verify tool call entries are not visible in the timeline.

- Session metadata panel shows format, duration, and token counts
<!-- category: core -->
Select a parsed session. Verify the metadata panel displays: agent format, session duration, total tokens, and cost (if available).

- Subagent entries render nested timelines
<!-- category: edge -->
Upload a Claude session with subagent (sidechain) entries. Verify the subagent timeline renders as a nested block within the main timeline.

- Empty session shows appropriate empty state
<!-- category: error -->
Upload a file that parses to zero timeline entries. Verify a meaningful empty-state message is shown instead of a blank screen.
Loading