- name
- mdproof-cli-e2e-test
- description
- Write and run E2E test runbooks that exercise the mdproof CLI itself. Use this skill whenever you need to: verify a new feature works end-to-end, validate a bug fix via a reproducible markdown runbook, test CLI flags (--dry-run, --fail-fast, --steps, --report json, --output), regression-test assertion types (substring, regex, exit_code, jq, snapshot), or confirm that parser/executor changes didn't break real-world runbook files. This skill produces .md files in runbooks/ that mdproof can run against itself — the tool testing itself. If you're about to write or run an E2E test for mdproof, use this skill first.
- argument-hint
- [feature-to-test | bug-to-reproduce | 'all']
- targets
- ["claude","codex"]
Write and run E2E test runbooks that validate mdproof through its own CLI. mdproof is a Markdown runbook test runner — the most natural way to E2E test it is with Markdown runbooks that exercise its features.
**Scope**: This skill creates `.md` test runbooks in `runbooks/` and runs them inside the devcontainer. It does NOT write Go code (use `implement-feature` for that).
## When to Use This
- After implementing a new feature — verify it works end-to-end
- After fixing a bug — create a regression test runbook
- Before a release — run all E2E runbooks to validate
- When Go unit tests pass but you suspect a CLI-level integration issue
## Architecture
```
runbooks/
├── 01-basics-proof.md # Core: version, help, dry-run, pass/fail
├── 02-assertions-proof.md # All 5 assertion types (substring, regex, exit_code, negation, snapshot)
├── 03-hooks-lifecycle-proof.md # build/setup/teardown + fail-fast
├── 04-advanced-proof.md # --steps, --from, --inline, --coverage
├── 05-config-strict-proof.md # mdproof.json config, strict mode
├── 06-output-formats-proof.md # JSON, file output, verbosity levels
├── 13-source-aware-proof.md # source-aware failures + removed flag regression
└── fixtures/ # Small runbooks used as test targets
├── hello-proof.md # Single step: echo hello → "hello"
├── failing-proof.md # Intentional assertion mismatch
├── multi-step-proof.md # 3 steps with shared env (export/use)
├── with-exit-code-proof.md # exit_code: 0 and exit_code: 1
├── with-regex-proof.md # regex: pattern matching
├── with-negated-proof.md # "Should NOT contain" assertion
├── with-snapshot-proof.md # snapshot: assertion (needs -u first run)
├── with-directives-proof.md # timeout/retry/depends HTML comments
├── with-inline.md # <!-- mdproof:start/end --> markers
├── fail-fast-proof.md # Multi-step where step 1 fails
├── source-aware-assert-proof.md # stable assertion-line fixture
├── source-aware-exit-proof.md # stable command-range fixture
├── source-aware-broken.md # parser error fixture
└── verbose-proof.md # Assertions visible with -v/-v -v
```
### File Naming Convention
mdproof's `ResolveFiles()` auto-discovers files matching: `*_runbook.md`, `*-runbook.md`, `*_proof.md`, `*-proof.md`. Name all runbooks and fixtures with `-proof.md` suffix. Exception: `with-inline.md` uses `--inline` flag which has its own discovery.
### ssenv — Isolated Test Environments
ssenv creates isolated HOME directories within the devcontainer for clean test execution. Scripts are in `.devcontainer/bin/` (on PATH inside the container).
```bash
ssenv create <name> # create isolated HOME at ~/.ss-envs/<name>/
ssenv enter <name> -- <cmd> # run command with isolated HOME
ssrm <name> # force-delete environment
ssls # list environments
```
**CRITICAL: ssenv changes CWD** — `ssenv enter` does `cd $HOME` (the isolated HOME), so all mdproof calls with relative paths MUST be wrapped:
```bash
# WRONG — fails because CWD is now the isolated HOME, not /workspace
ssenv enter test-foo -- mdproof runbooks/fixtures/hello-proof.md
# RIGHT — explicit cd to workspace first
ssenv enter test-foo -- bash -c "cd /workspace && mdproof runbooks/fixtures/hello-proof.md"
```
The ONE exception: commands that don't use file paths (like `mdproof --version`) work without wrapping:
```bash
ssenv enter test-foo -- mdproof --version # OK — no file path involved
```
## Workflow
### Phase 1: Environment Check
Verify devcontainer is running and binary is built:
```bash
DEVC="docker compose -f .devcontainer/docker-compose.yml exec -w /workspace mdproof-devcontainer"
# Check container is running
$DEVC mdproof --version || {
echo "Devcontainer not running. Start with: make devc-up"
exit 1
}
# Rebuild binary if needed (macOS host binary won't work in Linux!)
$DEVC make build
```
### Phase 2: Determine Scope
Based on $ARGUMENTS:
- **Feature name** (e.g., "fail-fast") → create/update specific runbook in `runbooks/`
- **Reporting change** (e.g., source-aware failure output) → add a focused runbook with stable line-number fixtures
- **Bug description** → create regression runbook as a new fixture + test step
- **"all"** → run all existing runbooks: `mdproof runbooks/`
### Phase 3: Write Test Runbook
Create a `.md` file that follows mdproof's own runbook format. The key pattern is **meta-testing**: the runbook's bash steps invoke `mdproof` against fixture runbooks via ssenv for isolation.
#### Template: Standard E2E Runbook with ssenv
```markdown
# <Feature Name>
<One-line description of what this validates.>
## Steps
### Step 1: Create isolated environment
` ``bash
ssenv create test-<feature>
echo "environment ready"
` ``
Expected:
- created: test-<feature>
### Step 2: Test the feature
` ``bash
ssenv enter test-<feature> -- bash -c "cd /workspace && mdproof runbooks/fixtures/hello-proof.md"
` ``
Expected:
- 1/1 passed
### Step N: Cleanup environment
` ``bash
ssrm test-<feature>
` ``
Expected:
- deleted: test-<feature>
```
#### Template: Testing Error Cases
```markdown
### Step N: Verify failure is detected
` ``bash
ssenv enter test-<feature> -- bash -c "cd /workspace && mdproof runbooks/fixtures/failing-proof.md 2>&1; echo EXIT=\$?"
` ``
Expected:
- EXIT=1
- 0/1 passed
```
### Phase 4: Execute
Run E2E runbooks inside the devcontainer. `mdproof` and ssenv scripts are on PATH via Dockerfile `ENV`:
```bash
DEVC="docker compose -f .devcontainer/docker-compose.yml exec -w /workspace mdproof-devcontainer"
# Single runbook
$DEVC mdproof runbooks/01-basics-proof.md
# All E2E runbooks (auto-discovers *-proof.md)
$DEVC bash -c 'mdproof runbooks/'
# With verbose output
$DEVC bash -c 'mdproof -v runbooks/'
```
### Phase 5: Report Results
Present results as:
```
E2E Results:
✓ 01-basics-proof.md — 8/8 passed
✓ 02-assertions-proof.md — 8/8 passed
✗ 04-advanced-proof.md — 7/8 passed, 1 failed
└─ Step 5: regex assertion did not match (expected ...)
```
If any runbook fails:
1. Show the failing step's stdout/stderr
2. Identify whether it's a **test bug** (wrong assertion) or a **code bug** (mdproof regression)
3. Fix and re-run
## Known Gotchas
**Read `.mdproof/lessons-learned.md` before writing runbooks.** It accumulates patterns and gotchas discovered through actual execution. Key ones:
1. `--steps`/`--from` summary counts all steps, not just selected
2. `--from N` skips earlier exports (persistent session)
3. macOS host binary won't work in Linux container
4. Snapshot files persist across runs
5. ssenv changes CWD to isolated HOME (wrap with `cd /workspace`)
6. Sandbox requires files inside CWD
After running runbooks, record new discoveries in `.mdproof/lessons-learned.md`.
> **Alternative**: Use `mdproof sandbox` for auto-container provisioning instead of manual devcontainer setup. See `skills/SKILL.md` § Container Safety.
## Assertion Patterns for Meta-Testing
When writing runbooks that test mdproof output, use these patterns:
### Exit code checking
```markdown
Expected:
- exit_code: 0
```
### JSON output with jq
```markdown
Expected:
- jq: .summary.total > 0
- jq: .summary.failed == 0
- jq: .steps[0].status == "passed"
- jq: .steps | length == 3
- jq: .steps[0].source.heading.start.line == 5
- jq: .steps[0].assertions[0].source.start.line == 13
```
### Substring in output
```markdown
Expected:
- passed
- Should NOT contain error
```
### Regex patterns
```markdown
Expected:
- regex: \d+ passed
- regex: duration.*\dms
```
### Source-aware failure output
```markdown
Expected:
- FAIL runbooks/fixtures/source-aware-assert-proof.md:13 Step 1: Assertion failure
- Assertion runbooks/fixtures/source-aware-assert-proof.md:13 expected output
- Command runbooks/fixtures/source-aware-exit-proof.md:7-10
```
### Negation (output must NOT contain)
```markdown
Expected:
- Should NOT contain ERROR
- No crash
- Must NOT contain panic
```
## Runbook Quality Checklist
Before committing a test runbook, verify:
- [ ] Uses ssenv for environment isolation (`ssenv create` at start, `ssrm` at end)
- [ ] Each step has a clear title describing what it tests
- [ ] `Expected` blocks use stable assertions (not timestamps/PIDs)
- [ ] Inner mdproof calls wrap with `bash -c "cd /workspace && mdproof ..."` (ssenv changes CWD)
- [ ] The runbook is self-contained — no ordering dependency between runbooks
- [ ] Running it twice produces the same result (idempotent)
- [ ] JSON assertions use `jq:` prefix for structured validation
- [ ] Error-case tests capture both exit code and error message: `2>&1; echo EXIT=\$?`
- [ ] Line-number assertions use purpose-built fixtures so source locations stay stable
- [ ] File named with `-proof.md` suffix for auto-discovery by `mdproof runbooks/`
- [ ] Temp files and snapshot dirs cleaned up in the final step
## Rules
- **All execution inside devcontainer** — mdproof refuses to run outside containers
- **Build inside container** — never use a host-built binary; `make build` inside the container
- **ssenv for isolation** — every runbook creates/destroys its own environment
- **Self-contained** — each runbook must work independently, no ordering dependency
- **Stable assertions** — no timestamps, PIDs, or other non-deterministic values
- **Clean up** — ssrm at end of each runbook; temp files in `/tmp/`
- **Meta-test pattern** — runbooks test mdproof by invoking mdproof (the binary under test)
- **Report failures clearly** — include stdout/stderr when a step fails
GitHubで見る