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) |
npx claudepluginhub memorysaver/agentic-engineering-patterns --plugin patternsWrites and runs unit, integration, e2e, performance, and contract tests to verify code functionality.
Scaffolds or migrates test-suite projects (API, E2E/UI, shared library) with archetype selection, scaffolding decisions, tag taxonomy, reporter stack, and CI integration.
Scaffolds new full-stack monorepos via Better-T-Stack or audits/onboards existing projects with agentic dev infrastructure (OpenSpec, workspace hooks, e2e-test skills).