From agentic-development-workflow
Sets up project-level quality infrastructure: workspace setup hook, e2e-test skill, and testing pyramid for phased build workflows. Use when adding tests or defining testing strategy.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agentic-development-workflow:testing-guideThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
How to set up the project-level quality infrastructure that the workflow plugin (`/aep-design` → `/aep-launch` → `/aep-build` → `/aep-wrap`) relies on.
How to set up the project-level quality infrastructure that the workflow plugin (/aep-design → /aep-launch → /aep-build → /aep-wrap) relies on.
Where this fits:
/aep-onboard → /aep-scaffold → /aep-testing-guide → [ /aep-design → /aep-launch → /aep-build → /aep-wrap ]
▲ you are here
/aep-scaffold creates the project. This guide creates the quality infrastructure. /aep-build uses it during feature development.
Every project needs two things before /aep-build can run autonomously:
.claude/hooks/workspace-setup.sh.claude/skills/e2e-test/A convention-based script that /aep-build calls during Phase 0 (workspace init) and session recovery (init.sh). The workflow plugin doesn't know your stack — this script does.
The hook MUST:
.dev-workflow/ports.env with at minimum:
WEB_PORT=<port>
SERVER_PORT=<port>
BASE_URL=http://localhost:<web-port>
SERVER_URL=http://localhost:<server-port>
The hook MAY:
.env files against .env.example templates#!/usr/bin/env bash
# Workspace Setup Hook
# Called by /aep-build Phase 0 and init.sh (session recovery)
#
# Contract: MUST write .dev-workflow/ports.env
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
cd "$REPO_ROOT"
# ── Detect workspace vs main ──
# AEP runs feature work in git worktrees at .feature-workspaces/<name>/.
# `git worktree list --porcelain` lists the main checkout first, so its first
# entry is the canonical main repo. Compare to detect whether we're in a worktree.
MAIN_REPO="$(git worktree list --porcelain 2>/dev/null | head -1 | sed 's/^worktree //')" || MAIN_REPO="$REPO_ROOT"
IS_WORKSPACE=false
[ "$REPO_ROOT" != "$MAIN_REPO" ] && IS_WORKSPACE=true
# ── PROJECT-SPECIFIC: Validate .env files ──
# Example for monorepo with multiple .env files:
# for example in apps/server/.env.example apps/web/.env.example; do
# env="${example%.example}"
# [ ! -f "$env" ] && cp "$example" "$env"
# done
# ── PROJECT-SPECIFIC: Install dependencies ──
# bun install
# npm install
# cargo build
# pip install -r requirements.txt
# ── Port scanning (parallel workspace isolation) ──
SERVER_PORT=3000
WEB_PORT=3001
while lsof -i :"$SERVER_PORT" -sTCP:LISTEN >/dev/null 2>&1 || \
lsof -i :"$WEB_PORT" -sTCP:LISTEN >/dev/null 2>&1; do
SERVER_PORT=$((SERVER_PORT + 10))
WEB_PORT=$((SERVER_PORT + 1))
done
# ── PROJECT-SPECIFIC: Update config with assigned ports ──
# sed -i '' "s|^SERVER_PORT=.*|SERVER_PORT=$SERVER_PORT|" apps/server/.env
# sed -i '' "s|^WEB_PORT=.*|WEB_PORT=$WEB_PORT|" apps/web/.env
# ── Write ports.env (CONTRACT — required) ──
mkdir -p .dev-workflow
cat > .dev-workflow/ports.env <<EOF
SERVER_PORT=$SERVER_PORT
WEB_PORT=$WEB_PORT
SERVER_URL=http://localhost:$SERVER_PORT
BASE_URL=http://localhost:$WEB_PORT
EOF
# ── PROJECT-SPECIFIC: Start dev server ──
# if ! lsof -ti :$SERVER_PORT >/dev/null 2>&1; then
# bun run dev &
# fi
# ── PROJECT-SPECIFIC: Seed database ──
# SCRIPT_DIR="$(cd "$(dirname "$0")/../skills/e2e-test/scripts" && pwd)"
# [ -f "$SCRIPT_DIR/seed.sh" ] && bash "$SCRIPT_DIR/seed.sh"
echo "Setup complete. Server: http://localhost:$SERVER_PORT Web: http://localhost:$WEB_PORT"
Make executable: chmod +x .claude/hooks/workspace-setup.sh
The hook will be called:
/aep-build Phase 0 (initial setup)init.sh run (session recovery after context reset)Design it to be safe to run multiple times — check if the dev server is already running before starting a new one, don't fail if dependencies are already installed.
A project-level skill that lives in .claude/skills/e2e-test/. It documents what tests exist, how to run them, and how to add new ones. The build agent reads this skill during Phases 6-8 to understand testing capabilities.
.claude/skills/e2e-test/
├── SKILL.md # Documents test infrastructure + how to add tests
└── scripts/
├── seed.sh # DB migrations + test account creation (idempotent)
└── <feature>-e2e.sh # One script per feature (added during /aep-build Phase 7)
---
name: e2e-test
description: E2E testing infrastructure for [PROJECT_NAME]. Use when running
tests, adding test coverage, or understanding what tests exist. Contains
setup scripts, test scripts, and patterns for adding new tests.
---
# E2E Test Infrastructure
## Prerequisites
- Dev server running (started by workspace-setup.sh hook)
- `.dev-workflow/ports.env` exists (written by workspace-setup.sh)
- [PROJECT-SPECIFIC: any other prerequisites]
## Setup
Source ports before running any test:
\`\`\`bash
source .dev-workflow/ports.env
\`\`\`
## Test Scripts
| Script | What it tests | Tools |
| ---------------- | ---------------------------- | ---------------------------- |
| seed.sh | DB migrations + test account | curl, sqlite3 |
| [feature]-e2e.sh | [description] | curl, optional agent-browser |
## Adding a New Test
1. Create `.claude/skills/e2e-test/scripts/<feature>-e2e.sh`
2. Follow the E2E script pattern (see below)
3. Add the script to the table above
4. Run it: `bash .claude/skills/e2e-test/scripts/<feature>-e2e.sh`
## E2E Script Pattern
All scripts follow this structure — copy it when creating new tests.
## Test Account
| Field | Value |
| -------- | ------------------ |
| Email | [PROJECT-SPECIFIC] |
| Password | [PROJECT-SPECIFIC] |
Every E2E test script should follow this pattern:
#!/usr/bin/env bash
# <Feature Name> E2E Test
#
# Prerequisites: dev server running, admin account seeded
# What it tests:
# - [test 1]
# - [test 2]
# - [test 3]
set -euo pipefail
# ── Port resolution ──
REPO_ROOT="$(git rev-parse --show-toplevel)"
if [ -f "$REPO_ROOT/.dev-workflow/ports.env" ]; then
source "$REPO_ROOT/.dev-workflow/ports.env"
fi
BASE_URL="${BASE_URL:-http://localhost:3001}"
SERVER_URL="${SERVER_URL:-http://localhost:3000}"
# ── Test helpers ──
PASS=0 FAIL=0 SKIP=0
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
skip() { echo " SKIP: $1"; SKIP=$((SKIP + 1)); }
agent_browser_healthy() {
command -v agent-browser >/dev/null 2>&1 || return 1
agent-browser navigate about:blank >/tmp/agent-browser-smoke.log 2>&1
}
# ── 1. [Test Group Name] ──
echo "=== 1. [Test Group] ==="
# API-level test (curl)
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$SERVER_URL/api/health")
if [ "$RESPONSE" = "200" ]; then
pass "Health endpoint returns 200"
else
fail "Health endpoint returned $RESPONSE"
fi
# Browser-level test (agent-browser)
if agent_browser_healthy; then
agent-browser navigate "$BASE_URL/login"
# ... browser assertions ...
pass "Login page loads"
else
skip "Login page (agent-browser unavailable or Chrome launch failed)"
fi
# ── 2. [Next Test Group] ──
echo "=== 2. [Next Group] ==="
# ...
# ── Results ──
echo ""
echo "=== Results ==="
echo " Passed: $PASS"
echo " Failed: $FAIL"
echo " Skipped: $SKIP"
[ "$FAIL" -gt 0 ] && exit 1
exit 0
Two categories of tests, owned by different parts of the system:
┌─────────────────────────────────┐ ┌──────────────────────────────────┐
│ Project test framework │ │ e2e-test skill │
│ (vitest, jest, pytest, etc.) │ │ (.claude/skills/e2e-test/) │
│ │ │ │
│ Unit tests │ │ API integration tests (curl) │
│ - co-located with source │ │ E2E browser tests (optional) │
│ - run during Phase 4 │ │ │
│ - use project's test command │ │ - live in scripts/ │
│ │ │ - run during Phase 5-8 │
│ Plugin doesn't manage this. │ │ Plugin orchestrates this. │
│ See your framework's docs. │ │ This guide covers this. │
└─────────────────────────────────┘ └──────────────────────────────────┘
Unit tests are owned by the project's test framework — vitest, jest, pytest, cargo test, go test. The monorepo setup from /aep-scaffold (or the project's own config) already configures the test runner.
During /aep-build Phase 4, the build agent runs the project's test command after implementing each task. No special setup from the e2e-test skill is needed.
The plugin doesn't teach unit testing. Your framework's docs do that.
| Aspect | Detail |
|---|---|
| What | Endpoint contracts: request/response shapes, auth, error codes |
| Runner | curl scripts or test framework with HTTP client |
| Scope | API boundaries, auth flows, error handling, data persistence |
| Convention | scripts/api/ or __tests__/api/ or inline in E2E scripts |
| When | After Phase 4 implementation, as part of Phase 5 verification |
| Who runs | Build agent during code review |
| Catches | Contract breaks, auth gaps, missing error handling, wrong status codes |
How to add:
# Test an API endpoint with curl:
source .dev-workflow/ports.env
# Test: POST /api/widget returns 201
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SERVER_URL/api/widget" \
-H "Content-Type: application/json" \
-d '{"name": "test"}')
STATUS=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -1)
if [ "$STATUS" = "201" ]; then
echo "PASS: Create widget returns 201"
else
echo "FAIL: Expected 201, got $STATUS"
fi
API tests can live inside E2E scripts (the curl-based sections) or as standalone scripts. For projects with many API endpoints, consider a dedicated scripts/api/ directory.
| Aspect | Detail |
|---|---|
| What | Full user flows through the UI |
| Runner | agent-browser when its Chrome smoke test passes; otherwise skip |
| Scope | Login, navigation, form submission, visual state, multi-step flows |
| Convention | .claude/skills/e2e-test/scripts/<feature>-e2e.sh |
| When | Phase 7, after dogfood exploration (Phase 6) identifies what to cover |
| Who runs | Build agent (Phase 7-8) + CI/CD |
| Catches | Integration failures, UI regressions, flow breaks, visual state errors |
Before adding browser-level tests on macOS, run:
agent-browser navigate about:blank
If Google Chrome crashes with _RegisterApplication, TransformProcessType, or abort() called, treat browser automation as unavailable on that machine. Keep API/unit tests running and mark browser checks SKIP until Chrome or agent-browser is fixed locally.
How to add: Follow the E2E Script Pattern in Part 2.
Features start with zero tests and build up through /aep-build phases:
Phase 4 (implement) → run project's unit tests (framework-level)
Phase 5 (review) → API contract tests via e2e-test scripts
Phase 6 (dogfood) → browser exploration when available, find gaps
Phase 7 (e2e) → codify findings into e2e-test scripts
Phase 8 (review) → run all e2e-test scripts + unit tests
Each layer catches different failure modes:
Not every project needs both e2e-test layers. Unit tests are always the project framework's responsibility — this table covers what the e2e-test skill should include:
| Project type | API tests (curl) | E2E browser (optional) |
|---|---|---|
| Full-stack web app | Yes | Yes |
| API-only service | Yes | Skip |
| CLI tool | Skip | Skip |
| Static site / landing page | Skip | E2E only |
| Library / package | Skip | Skip |
| Mobile app (API backend) | Yes | Skip (use native testing) |
For the first feature, focus on proving the test infrastructure works:
Don't worry about coverage. The goal is one green test end-to-end.
Each subsequent feature adds tests via the e2e-test skill:
Unit tests are added alongside code during Phase 4 using the project's test framework — the e2e-test skill doesn't manage these.
The build agent follows the e2e-test SKILL.md to know where to put scripts and what patterns to use.
In full mode (/aep-launch with evaluator), the evaluator agent reads .dev-workflow/feature-verification.json. Verification steps should map to test assertions where possible:
{
"task": "Add user profile page",
"verification_steps": [
"GET /api/user/profile returns 200 with user data",
"Profile page renders user name and email",
"Edit profile form saves changes"
]
}
Each verification step becomes a test assertion — either in an API test (step 1) or E2E script (steps 2-3).
E2E scripts in .claude/skills/e2e-test/scripts/ are designed to run in CI:
ports.env with fallback defaultsAdd them to your CI pipeline:
# Example: GitHub Actions
- name: Run E2E tests
run: |
for script in .claude/skills/e2e-test/scripts/*-e2e.sh; do
echo "Running $script..."
bash "$script" || exit 1
done
| What | Where | Managed by |
|---|---|---|
| Workspace setup hook | .claude/hooks/workspace-setup.sh | Project (you create this) |
| E2E test skill | .claude/skills/e2e-test/SKILL.md | Project (you create this) |
| Seed script | .claude/skills/e2e-test/scripts/seed.sh | e2e-test skill |
| API contract tests | .claude/skills/e2e-test/scripts/<feature>-e2e.sh | e2e-test skill (Phase 5) |
| E2E browser tests | .claude/skills/e2e-test/scripts/<feature>-e2e.sh | e2e-test skill (Phase 6-7) |
| Unit tests | Co-located with source | Project test framework (Phase 4) |
| Feature verification | .dev-workflow/feature-verification.json | /aep-build plugin (Phase 5) |
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub memorysaver/agentic-engineering-patterns --plugin agentic-development-workflow