specs-e2e-verification について
実際の実行時動作に基づいて、実装済みの仕様を検証するスキルです。ローカル環境を判定して起動し、`[IMP]` の受け入れ基準からテストを作成、結果を基準に対応付けてレポートを生成し、環境を終了します。
主な機能
- 01アプリケーション種別とポートを検出
- 02ローカル環境を起動して確認
- 03`[IMP]` 基準からテストを作成
- 04テスト結果を基準に対応付け
- 05検証レポートを生成
- 06実行環境を終了
活用シーン
- 01実装後の機能検証
- 02稼働中の API 動作確認
- 03仕様完了前の検証記録作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill specs-e2e-verificationインストール後、Claude Code で/specs-e2e-verificationを入力して読み込まれたか確認します。
同梱ファイルreferences/test-execution-patterns.md
詳細
仕様の E2E 検証
概要
仕様の実装とクリーンアップが完了した後に、実環境での検証を行います。このスキルは、ユニットテスト済みのコードと、実行時に観察できる動作とのギャップを次の手順で埋めます。
- プロジェクトファイルからアプリケーションの種類を検出する
- ローカルのランタイムを起動する(Docker Compose、開発サーバー、Spring Boot など)
- 機能仕様の
[IMP]受け入れ条件からテストを導出する - 実際のテストを実行する(
curl、Playwright、コンピューター操作) - 結果を受け入れ条件に対応付ける
- 検証レポートを生成する
- 環境を終了する
入力:docs/specs/[id]/(機能仕様とタスクを含む仕様フォルダー)
出力:docs/specs/[id]/e2e-report-YYYY-MM-DD-HHMMSS.md
使用するタイミング
- 機能が実際に動作することを確認するため、
specs.task-implementationとspecs.code-cleanupの後に使用します。 - 開発者が「実際にテストして」「API が本当に動作するか検証して」「E2E チェックを実行して」「受け入れ条件を実環境で検証して」と依頼した場合に使用します。
- 仕様を完了とする前に、機能の完了を示す証拠を生成するために使用します。
- ユニットテスト、静的解析、コードレビューには使用しないでください。このスキルは実行時の動作検証のみを対象とします。
引数
| 引数 | 必須 | 説明 |
|---|---|---|
--spec | はい | 仕様フォルダーへのパス(例:docs/specs/001-feature/) |
--task | いいえ | 検証範囲を限定する特定のタスク ID(例:TASK-003) |
--keep-alive | いいえ | 指定した場合、終了処理を省略して環境を起動したままにします |
--timeout | いいえ | 起動とテストのタイムアウト秒数(デフォルト:120) |
--insecure | いいえ | 指定した場合、curl で -k / --insecure の使用を許可します(TLS バイパスのオプトイン) |
ベストプラクティス
- 安全を最優先:破壊的なコマンド(
rm -rf、docker system prune、sudo)は実行しないでください。 - 推測せずに検出:ファイルのヒューリスティックを使ってアプリケーションの種類を判定し、曖昧な場合にのみユーザーに確認してください。
- 受け入れ条件に基づく:すべてのテストは、機能仕様の
[IMP]受け入れ条件にたどれる必要があります。 - クリーンアップ:
--keep-aliveが指定されていない限り、必ず終了処理を行ってください。残存プロセスがある場合は警告してください。 - コードを変更しない:このスキルはソースコードに対して読み取り専用です。レポートは作成できますが、ロジックにパッチを適用することはありません。
- TodoWrite を使用:8 つのフェーズすべての進捗を追跡してください。
手順
フェーズ 1:引数の解析とコンテキストの読み込み
-
$ARGUMENTSを解析します。--spec(必須):仕様フォルダーのパスです。ディレクトリが存在し、機能仕様ファイル(YYYY-MM-DD--*.md)が少なくとも 1 つ含まれていることを検証します。存在しないか無効な場合は、エラーで中止します。--task(任意):タスク ID フィルターです(例:TASK-003)。指定された場合は、仕様フォルダー内にtasks/<task-id>.mdが存在することを検証します。--keep-alive(任意):ブール値のフラグです。指定された場合、最後の終了処理を省略します。--timeout(任意):秒数を表す正の整数です。デフォルトは120です。値が正の整数であることを検証し、そうでない場合はエラーで中止します。--insecure(任意):ブール値のフラグです。指定された場合、自己署名証明書を使うローカル開発では curl コマンドで-k/--insecureを使用できます。デフォルトでは TLS バイパスは禁止されています(REQ-NR003)。
-
機能仕様を読み込み、次の内容を抽出します。
- 分類タグ(
[IMP]、[SEF]、[EXT])が付いたすべての受け入れ条件 - 実行時テストを生成するのは
[IMP]条件のみです
- 分類タグ(
-
--taskが指定されている場合は、タスクファイルを読み込み、そのprovidesファイルと関連する受け入れ条件に範囲を限定します。 -
TodoWriteを使って、8 つのフェーズすべての Todo リストを作成します。
フェーズ 1.5:セキュリティ検証ゲート
コマンドを実行する前に、次のセキュリティチェックを実行します。
-
コマンドのホワイトリストチェック:
- フェーズ 3 で導出した起動コマンドは、
references/test-execution-patterns.mdに記載されたパターンに一致する必要があります。 - コマンドがホワイトリストに含まれていない場合 →
AskUserQuestionを使用して、実行前にユーザーへ明示的な確認を求めます。 - ホワイトリストに含まれるコマンドは次のとおりです:
docker compose up -d --build、./mvnw spring-boot:run、./gradlew bootRun、npm run dev、npm run start:dev、npm start、cargo tauri build --debug、cargo tauri dev、npm run electron:dev、npx electron .、open *.app、および同等のローカルプロセス起動コマンドです。 sudo、rm -rf、docker system prune、mkfs、dd、または同様の破壊的操作を含むコマンドはホワイトリストに含まれず、拒否する必要があります。
- フェーズ 3 で導出した起動コマンドは、
-
禁止パターンのスキャン(REQ-NR001):
- 導出した起動コマンドと生成したすべてのテストコマンドを、次のパターンについてスキャンします:
sudo→ 次のメッセージを表示して中止します:"Forbidden: sudo is not permitted during E2E verification."rm -rf→ 次のメッセージを表示して中止します:"Forbidden: rm -rf is not permitted during E2E verification."docker system prune→ 次のメッセージを表示して中止します:"Forbidden: docker system prune is not permitted during E2E verification."- データベース、ボリューム、またはローカルデータを対象とする
rm、drop、destroy、prune→ 次のメッセージを表示して中止します:"Forbidden: destructive data operations are not permitted."
- 禁止パターンが検出された場合 → 直ちに中止し、起動に進んではなりません。
- 導出した起動コマンドと生成したすべてのテストコマンドを、次のパターンについてスキャンします:
-
TLS 適用チェック(REQ-NR003):
- 生成した
curlコマンドについて:-kまたは--insecureが含まれ、かつ--insecureが渡されていない場合 → 次のメッセージを表示して中止します:"Forbidden: curl TLS bypass (-k / --insecure) is disabled by default. Pass --insecure to opt-in."--insecureが渡されている場合 → 警告を記録します:"WARNING: TLS certificate verification is disabled. Use only for local development."
- 生成した
-
データ整合性の事前チェック(REQ-NR004):
- 起動コマンドを実行する前に、既存データを上書きまたは削除するパターン(例:
rm、drop、prune、ボリューム削除フラグ)がないか確認します。 - コマンドが既存のデータベース、ボリューム、またはローカルデータディレクトリを変更する場合 → 次のメッセージを表示して中止します:"Forbidden: startup commands must not overwrite or delete existing data."
- 起動コマンドを実行する前に、既存データを上書きまたは削除するパターン(例:
フェーズ 2:アプリケーションの種類を検出し、ポートを特定する
-
.gitを含むディレクトリ、または--specの親ディレクトリをPROJECT_ROOTに設定します。 -
以下のヒューリスティックを使って
PROJECT_ROOT内の設定ファイルを調べます(記載順に実行します)。Docker 管理(最優先):
[ -f "$PROJECT_ROOT/docker-compose.yml" ] || [ -f "$PROJECT_ROOT/docker-compose.yaml" ] || [ -f "$PROJECT_ROOT/compose.yml" ]これらのファイルのいずれかが存在する場合は、他のフレームワーク設定にかかわらず、Docker 管理に分類します。
JVM/Spring Boot:
[ -f "$PROJECT_ROOT/pom.xml" ] || [ -f "$PROJECT_ROOT/build.gradle" ] || [ -f "$PROJECT_ROOT/build.gradle.kts" ]AND ソースディレクトリが存在することを確認します:
[ -d "$PROJECT_ROOT/src/main/java" ]両方の条件を満たす場合は、JVM ベースのサービスに分類します。
NestJS:
[ -f "$PROJECT_ROOT/package.json" ] && grep -q '"@nestjs/core"' "$PROJECT_ROOT/package.json"条件を満たす場合は、NestJSに分類します。
Web SPA(React/Vue/Angular):
[ -f "$PROJECT_ROOT/package.json" ] && ( grep -q '"react"' "$PROJECT_ROOT/package.json" || grep -q '"vue"' "$PROJECT_ROOT/package.json" || grep -q '"@angular/core"' "$PROJECT_ROOT/package.json" )条件を満たす場合は、Web SPAに分類します。
デスクトップアプリ:
[ -f "$PROJECT_ROOT/src-tauri/Cargo.toml" ] || ( [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"electron"' "$PROJECT_ROOT/package.json" ) || [ -n "$(find "$PROJECT_ROOT" -maxdepth 2 -name '*.csproj' -print -quit 2>/dev/null)" ]条件を満たす場合は、デスクトップアプリに分類します。
Python:
[ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ] || [ -f "$PROJECT_ROOT/app.py" ] || [ -f "$PROJECT_ROOT/manage.py" ]条件を満たす場合は、Pythonに分類します。
-
優先順位のルールを適用します:
- Docker Compose は常に優先されます:Docker Compose ファイルとフレームワーク設定の両方が存在する場合は、Docker 管理に分類します。Compose スタックが実行環境を定義します。
- Docker 以外の設定が複数検出された場合(例:Docker Compose なしで
pom.xmlとpackage.jsonの両方が存在する場合):- 仕様のドメインからバックエンドとフロントエンドのどちらかが明確に分かる場合(例:仕様のタイトルに「API」、「backend」、「service」が含まれる場合)は、JVM または NestJS を優先します。
- 仕様のドメインからフロントエンドであることが明確に分かる場合(例:仕様のタイトルに「UI」、「page」、「component」が含まれる場合)は、Web SPA を優先します。
- それでも判断できない場合は、ユーザーへの質問に進みます(ステップ 4)。
-
認識可能な設定が見つからない場合、または Docker 以外の設定が複数存在し、仕様のドメインが曖昧な場合は、
AskUserQuestionを使い、次の選択肢をそのまま提示します:- "REST API"
- "Web SPA"
- "Desktop"
- "Skip"
-
ポートの特定:アプリケーションの種類が判明したら、次の順にフレームワーク設定ファイルを調べ、対象ポートを特定します。
-
Vite プロジェクト(
vite.config.tsまたはvite.config.js):grep -oE 'port:\s*[0-9]+' "$PROJECT_ROOT/vite.config.ts" 2>/dev/null | grep -oE '[0-9]+' || \ grep -oE 'port:\s*[0-9]+' "$PROJECT_ROOT/vite.config.js" 2>/dev/null | grep -oE '[0-9]+' -
Spring Boot(
application.yml):grep -A5 '^server:' "$PROJECT_ROOT/src/main/resources/application.yml" 2>/dev/null | grep 'port:' | head -1 | tr -dc '0-9' -
Spring Boot(
application.properties):grep '^server.port=' "$PROJECT_ROOT/src/main/resources/application.properties" 2>/dev/null | cut -d= -f2 | tr -dc '0-9' -
Node.js/package.json のスクリプト:
grep -oE -- '--port [0-9]+' "$PROJECT_ROOT/package.json" 2>/dev/null | grep -oE '[0-9]+' | head -1スクリプト内の
PORT環境変数も確認します:grep -oE 'PORT=[0-9]+' "$PROJECT_ROOT/package.json" 2>/dev/null | grep -oE '[0-9]+' | head -1 -
フォールバックのデフォルト値(どの設定ファイルにもポートが見つからない場合):
アプリケーションの種類 デフォルトポート Node.js/NestJS 3000 Spring Boot(JVM) 8080 Angular 4200 Vite(React/Vue) 5173 Python 8000
-
-
検出した種類と特定したポートを記録します。どちらもレポートに記載されます。
フェーズ 3:環境を起動し、準備完了を待つ
コマンドの対応表については、references/test-execution-patterns.md(このスキルに同梱)を読みます。検出結果に基づいて処理します。
データ整合性の事前確認(REQ-NR004):起動コマンドを実行する前に、既存のデータベース、ボリューム、またはローカルデータを上書きまたは削除するパターン(例:rm、--volumes、prune、drop)が含まれていないことを確認します。破壊的なパターンが検出された場合は、直ちに次のメッセージを表示して中止します:"起動を中止しました:コマンドによって既存のデータが破壊される可能性があります。"
-
ランタイム状態を初期化します:
STARTUP_COMMAND=""— 環境の起動に使用する正確なコマンドHEALTH_CHECK_METHOD=""— 準備完了をどのように判定したかの説明STARTUP_LOGS_FILE="$(mktemp)"— 起動時の stdout/stderr を記録する一時ファイルSTARTUP_TIMEOUT="${TIMEOUT:-120}"— 準備完了まで待機する秒数START_TIME="$(date +%s)"STARTUP_PID=""— バックグラウンドプロセスの PID(ローカルプロセス用)
-
起動前のポート確認(すべての種類):
if lsof -i :"$TARGET_PORT" >/dev/null 2>&1 || nc -z localhost "$TARGET_PORT" 2>/dev/null; then echo "Port $TARGET_PORT is already in use." EXISTING_PID=$(lsof -ti:"$TARGET_PORT" | head -n1) EXISTING_CMD=$(ps -p "$EXISTING_PID" -o comm= 2>/dev/null || echo "unknown") if [ "$APP_TYPE" = "Docker-managed" ] && docker ps --format '{{.Names}}' 2>/dev/null | grep -q "$EXISTING_CMD"; then echo "Existing Docker container detected on port $TARGET_PORT; reusing it." STARTUP_COMMAND="(existing container reused)" elif [ "$APP_TYPE" = "JVM-based service" ] && echo "$EXISTING_CMD" | grep -q "java"; then echo "Existing Java process detected on port $TARGET_PORT; reusing it." STARTUP_COMMAND="(existing Java process reused)" else echo "Port $TARGET_PORT is occupied by an unrelated process ($EXISTING_CMD). Free the port and retry." exit 1 fi fi -
Docker 管理下での起動:
- Docker デーモンに接続できることを確認します:
if ! docker info >/dev/null 2>&1; then echo "Docker を利用できません。Docker Desktop を起動するか、フレームワークを直接起動してください。" exit 1 fi STARTUP_COMMAND="docker compose up -d --build"を設定します- 次を実行します:
cd "$PROJECT_ROOT" && docker compose up -d --build >> "$STARTUP_LOGS_FILE" 2>&1 - ヘルスチェックループ(タイムアウトを適用):
HEALTH_CHECK_METHOD="docker compose ps --format json" READY=false while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do # オプション A:Docker ネイティブのヘルスステータス(JSON 配列と NDJSON の両方に対応) if docker compose ps --format json 2>/dev/null | jq -s -e '.[] | select(.Health=="healthy")' >/dev/null 2>&1; then READY=true HEALTH_CHECK_METHOD="docker compose ps (HEALTHCHECK=healthy)" break fi # オプション B:HEALTHCHECK が定義されていない場合のフォールバックとしてポートをポーリング if curl -sf "http://localhost:${TARGET_PORT}" >/dev/null 2>&1; then READY=true HEALTH_CHECK_METHOD="port polling via curl on localhost:${TARGET_PORT} (every 3s)" break fi sleep 3 done if [ "$READY" != "true" ]; then echo "Docker Compose の起動がタイムアウトしました(${STARTUP_TIMEOUT}s)。" docker compose logs --tail=50 >> "$STARTUP_LOGS_FILE" 2>&1 # 孤立したコンテナを避けるため、クリーンアップを試みます(REQ-NR006) docker compose down >/dev/null 2>&1 || true exit 1 fi
- Docker デーモンに接続できることを確認します:
-
Spring Boot の起動:
- ビルドツールを判定し、ログを記録しながらバックグラウンドで起動します:
if [ -f "$PROJECT_ROOT/pom.xml" ]; then cd "$PROJECT_ROOT" && ./mvnw spring-boot:run -Dspring-boot.run.profiles=e2e >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="./mvnw spring-boot:run -Dspring-boot.run.profiles=e2e" elif [ -f "$PROJECT_ROOT/build.gradle" ] || [ -f "$PROJECT_ROOT/build.gradle.kts" ]; then cd "$PROJECT_ROOT" && ./gradlew bootRun --args='--spring.profiles.active=e2e' >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="./gradlew bootRun --args='--spring.profiles.active=e2e'" fi - ヘルスチェックループ(タイムアウトを適用):
HEALTH_CHECK_METHOD="Spring Boot actuator /actuator/health" READY=false while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do # オプション A:Actuator のヘルスエンドポイント if curl -sf "http://localhost:${TARGET_PORT}/actuator/health" >/dev/null 2>&1; then READY=true HEALTH_CHECK_METHOD="Spring Boot actuator /actuator/health" break fi # オプション B:フォールバックとしてポートの応答を確認 if nc -z localhost "$TARGET_PORT" 2>/dev/null; then READY=true HEALTH_CHECK_METHOD="port polling via nc on localhost:${TARGET_PORT} (every 3s)" break fi # バックグラウンドプロセスが早期に終了した場合は、ただちに失敗とする if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then echo "Spring Boot process exited before reaching healthy state." break fi sleep 3 done if [ "$READY" != "true" ]; then echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Spring Boot." # レポート用に起動ログの末尾を取得 tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true # クリーンアップを試みる(REQ-NR006) [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true exit 1 fi
- ビルドツールを判定し、ログを記録しながらバックグラウンドで起動します:
-
NestJS / Node.js の起動:
- 原因が分かりにくいエラーを避けるため、
node_modulesが存在することを確認します:if [ ! -d "$PROJECT_ROOT/node_modules" ]; then echo "node_modules not found. Run 'npm install' before verification." exit 1 fi package.jsonのスクリプトから起動コマンドを判定します:if [ "$APP_TYPE" = "NestJS" ] && grep -q '"start:dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then cd "$PROJECT_ROOT" && npm run start:dev >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npm run start:dev" elif grep -q '"dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then cd "$PROJECT_ROOT" && npm run dev >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npm run dev" else cd "$PROJECT_ROOT" && npm start >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npm start" fi- ヘルスチェックループ(タイムアウトを適用):
HEALTH_CHECK_METHOD="port polling via nc/curl on localhost:${TARGET_PORT} (every 3s)" READY=false while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do # Option A: HTTP readiness via curl if curl -sf "http://localhost:${TARGET_PORT}" >/dev/null 2>&1; then READY=true HEALTH_CHECK_METHOD="curl on localhost:${TARGET_PORT}" break fi # Option B: Raw port readiness via nc if nc -z localhost "$TARGET_PORT" 2>/dev/null; then READY=true HEALTH_CHECK_METHOD="nc -z localhost:${TARGET_PORT}" break fi # Fail fast if the background process exited early if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then echo "Node.js/NestJS process exited before reaching healthy state." break fi sleep 3 done if [ "$READY" != "true" ]; then echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Node.js/NestJS." tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true # Attempt cleanup (REQ-NR006) [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true exit 1 fi - コンソールの準備完了検出:準備完了を確認した後、
$STARTUP_LOGS_FILEをスキャンして、一般的なサーバー準備完了メッセージ(例:Nest application successfully started、Local:、ready in、Server running)を探し、最初に一致した行を起動成功の証拠としてレポートに記録します。
- 原因が分かりにくいエラーを避けるため、
-
Python(FastAPI / Django / Flask)(
references/test-execution-patterns.mdを参照):uvicorn main:app --reload、python manage.py runserver、またはflask runを実行します- ポートの準備が整うまで待機します
-
デスクトップアプリ(Tauri / Electron / .NET MAUI)(
references/test-execution-patterns.mdを参照):フレームワークの検出:
DESKTOP_FRAMEWORK="" if [ -f "$PROJECT_ROOT/src-tauri/Cargo.toml" ]; then DESKTOP_FRAMEWORK="tauri" elif [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"electron"' "$PROJECT_ROOT/package.json"; then DESKTOP_FRAMEWORK="electron" elif [ -n "$(find "$PROJECT_ROOT" -maxdepth 2 -name '*.csproj' -print -quit 2>/dev/null)" ]; then DESKTOP_FRAMEWORK="dotnet-maui" fiビルド手順(ビルド済みのデバッグバイナリが存在しない場合、またはソースがターゲットより新しい場合に実行):
- Tauri:
if [ "$DESKTOP_FRAMEWORK" = "tauri" ]; then if ! command -v cargo >/dev/null 2>&1; then echo "ERROR: Rust/Cargo is not installed. Tauri build requires cargo." exit 1 fi BUILD_COMMAND="cargo tauri build --debug" cd "$PROJECT_ROOT" && $BUILD_COMMAND >> "$STARTUP_LOGS_FILE" 2>&1 BUILD_EXIT_CODE=$? if [ "$BUILD_EXIT_CODE" -ne 0 ]; then echo "Tauri build failed (exit code $BUILD_EXIT_CODE). See startup logs for details." exit 1 fi STARTUP_COMMAND="$BUILD_COMMAND (build succeeded)" fi - Electron:
if [ "$DESKTOP_FRAMEWORK" = "electron" ]; then if [ ! -d "$PROJECT_ROOT/node_modules" ]; then echo "ERROR: node_modules not found. Run 'npm install' before verification." exit 1 fi BUILD_COMMAND="(no separate build required for dev mode)" STARTUP_COMMAND="npm run electron:dev" if grep -q '"electron:build"' "$PROJECT_ROOT/package.json" 2>/dev/null; then BUILD_COMMAND="npm run electron:build" cd "$PROJECT_ROOT" && $BUILD_COMMAND >> "$STARTUP_LOGS_FILE" 2>&1 BUILD_EXIT_CODE=$? if [ "$BUILD_EXIT_CODE" -ne 0 ]; then echo "Electron build failed (exit code $BUILD_EXIT_CODE). See startup logs for details." exit 1 fi fi fi
ビルド済みアプリケーションのバイナリを起動する:
-
Tauri(macOS):
APP_BUNDLE=$(find "$PROJECT_ROOT/src-tauri/target/debug/bundle" -name "*.app" -print -quit 2>/dev/null) if [ -n "$APP_BUNDLE" ]; then open "$APP_BUNDLE" >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="open $APP_BUNDLE" else DEV_BINARY=$(find "$PROJECT_ROOT/src-tauri/target/debug" -maxdepth 1 -type f -executable ! -name '*.dylib' ! -name '*.so' -print -quit 2>/dev/null) if [ -n "$DEV_BINARY" ]; then "$DEV_BINARY" >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="$DEV_BINARY" else cargo tauri dev >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="cargo tauri dev" fi fi -
Tauri(Linux):
APP_BINARY=$(find "$PROJECT_ROOT/src-tauri/target/debug" -maxdepth 1 -type f -executable ! -name '*.so' -print -quit 2>/dev/null) if [ -n "$APP_BINARY" ]; then "$APP_BINARY" >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="$APP_BINARY" else cargo tauri dev >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="cargo tauri dev" fi -
Electron:
if grep -q '"electron:dev"' "$PROJECT_ROOT/package.json" 2>/dev/null; then cd "$PROJECT_ROOT" && npm run electron:dev >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npm run electron:dev" elif grep -q '"start"' "$PROJECT_ROOT/package.json" 2>/dev/null; then cd "$PROJECT_ROOT" && npm start >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npm start" else cd "$PROJECT_ROOT" && npx electron . >> "$STARTUP_LOGS_FILE" 2>&1 & STARTUP_PID=$! STARTUP_COMMAND="npx electron ." fi
ヘルスチェック(プロセスの出現を確認、タイムアウトを適用):
HEALTH_CHECK_METHOD="process polling via ps/kill -0 (every 3s)" READY=false while [ $(( $(date +%s) - START_TIME )) -lt "$STARTUP_TIMEOUT" ]; do if [ -n "$STARTUP_PID" ] && kill -0 "$STARTUP_PID" 2>/dev/null; then READY=true HEALTH_CHECK_METHOD="process PID ${STARTUP_PID} confirmed alive" break fi sleep 3 done if [ "$READY" != "true" ]; then echo "Startup timeout (${STARTUP_TIMEOUT}s) exceeded for Desktop app." tail -n 100 "$STARTUP_LOGS_FILE" >> "$STARTUP_LOGS_FILE".final 2>&1 || true [ -n "$STARTUP_PID" ] && kill -TERM "$STARTUP_PID" 2>/dev/null || true exit 1 fi - Tauri:
-
起動後の記録処理(すべての種類):
- レポートのメタデータに
STARTUP_COMMANDとHEALTH_CHECK_METHODを記録します(AC-010)。 STARTUP_LOGS_FILEの内容をRaw Outputとしてレポートに追記します(REQ-020)。- 起動に失敗した場合は、理由を問わず、利用可能なすべてのログを取得し、明確なエラーメッセージを出して中止します。
- レポートのメタデータに
フェーズ4:テストの生成と実行
重要:[IMP]の受け入れ基準のみをテストします。それぞれを、具体的な実行時アクションに1つ以上変換します。
REST API テスト(curl)
前提条件の確認:テストを生成する前に、curlがインストールされていることを確認します:
if ! command -v curl >/dev/null 2>&1; then
echo "ERROR: curl is not installed."
echo "Install instructions:"
echo " macOS: brew install curl"
echo " Ubuntu: sudo apt-get install curl"
echo " Windows: choco install curl or winget install curl"
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "WARNING: jq is not installed. JSON body assertions will fall back to grep (less precise)."
echo "Install instructions:"
echo " macOS: brew install jq"
echo " Ubuntu: sudo apt-get install jq"
echo " Windows: choco install jq or winget install jqlang.jq"
fi
1. エンドポイントのヒントについて[IMP] ACを解析する
仕様内の各 [IMP] 受け入れ基準について、以下を実行します。
- キーワード
GET、POST、PUT、PATCH、DELETE(大文字と小文字を区別しません)を検索して、HTTP メソッドを抽出します。 /api/users、/v1/healthなど、/の後に英数字のセグメントが続くパターンを検索して、エンドポイントパスを抽出します。- 数値パターン
2xx、3xx、4xx、5xx、または200、201、204、400、401、403、404、500などの特定のコードを検索して、期待される HTTP ステータスコードを抽出します。 application/json、text/plain、text/htmlなどを検索して、期待されるContent-Typeを抽出します。- AC のテキストから、期待されるレスポンス本文のヒント(フィールド名、配列の有無、文字列値)を抽出します。
[IMP] AC に解析可能なエンドポイントパスとメソッドが含まれていない場合は、MANUAL CHECK REQUIRED とマークし、次の基準に進みます。
2. 認証情報を検出する
curl コマンドを作成する前に、PROJECT_ROOT 内の以下のファイルを順番にスキャンして、テスト用認証情報を探します。
| ファイル | キーパターン |
|---|---|
.env.test | E2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=... |
.env.local | E2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=... |
application-test.yml | e2e.auth-token: ...、e2e.username: ...、e2e.password: ... |
application-test.properties | e2e.auth-token=...、e2e.username=...、e2e.password=... |
e2e.credentials.json | トップレベルのキー E2E_AUTH_TOKEN、E2E_USERNAME、E2E_PASSWORD |
検出ロジック:
# .env files
[ -f "$PROJECT_ROOT/.env.test" ] && export $(grep -E '^(E2E_AUTH_TOKEN|E2E_USERNAME|E2E_PASSWORD)=' "$PROJECT_ROOT/.env.test" | xargs)
[ -f "$PROJECT_ROOT/.env.local" ] && export $(grep -E '^(E2E_AUTH_TOKEN|E2E_USERNAME|E2E_PASSWORD)=' "$PROJECT_ROOT/.env.local" | xargs)
# Spring YAML
[ -f "$PROJECT_ROOT/src/main/resources/application-test.yml" ] && \
E2E_AUTH_TOKEN=$(grep -A1 'e2e:' "$PROJECT_ROOT/src/main/resources/application-test.yml" | grep 'auth-token:' | sed 's/.*: *//')
# Spring properties
[ -f "$PROJECT_ROOT/src/main/resources/application-test.properties" ] && \
E2E_AUTH_TOKEN=$(grep '^e2e.auth-token=' "$PROJECT_ROOT/src/main/resources/application-test.properties" | cut -d= -f2-)
# JSON credentials file
[ -f "$PROJECT_ROOT/e2e.credentials.json" ] && \
E2E_AUTH_TOKEN=$(jq -r '.E2E_AUTH_TOKEN // empty' "$PROJECT_ROOT/e2e.credentials.json")
すべてのファイルをスキャンしても認証情報が見つからず、かつ AC のテキストから認証が必要だと判断できる場合(「auth」、「login」、「token」、「protected」、「bearer」、「API key」に言及している場合)は、AskUserQuestion を使用してユーザーに以下を尋ねます。
- 「E2E_AUTH_TOKEN を入力してください(何も入力しない場合は、認証情報なしとして扱います)」
- 「E2E_USERNAME を入力してください(何も入力しない場合は、認証情報なしとして扱います)」
- 「E2E_PASSWORD を入力してください(何も入力しない場合は、認証情報なしとして扱います)」
セキュリティ:E2E レポートではトークンの値を伏せ、ヘッダー名のみを表示します(例:Authorization: Bearer <redacted>)。
3. curl コマンドを生成する
解析可能な各 [IMP] AC について、以下の正確な形式で curl コマンドを作成します。
curl -s -w "\n%{http_code}" -o /tmp/e2e_resp.json \
-X <METHOD> \
-H "Content-Type: application/json" \
<AUTH_HEADER> \
-d '<REQUEST_BODY>' \
"http://localhost:${TARGET_PORT}<PATH>"
ルール:
- 常に
-s -w "\n%{http_code}" -o /tmp/e2e_resp.jsonを含めます。 -X <METHOD>:メソッドがGET以外の場合にのみ追加します。GETの場合は-Xを完全に省略します。-H "Content-Type: application/json":POST、PUT、PATCHの場合にのみ追加します。-d '<REQUEST_BODY>':AC でリクエスト本文が説明されている場合にのみ追加します。本文が説明されていない場合は-dを省略します。<AUTH_HEADER>:E2E_AUTH_TOKENが設定されている場合:-H "Authorization: Bearer ${E2E_AUTH_TOKEN}"E2E_USERNAMEとE2E_PASSWORDが設定されている場合:-u "${E2E_USERNAME}:${E2E_PASSWORD}"- 認証情報が見つからない場合:認証ヘッダーを省略します。
- TLS の適用(REQ-NR003):スキルの実行時に
--insecureフラグが明示的に渡されない限り、curl コマンドに-kまたは--insecureを決して追加しないでください。--insecureが渡された場合は、TLS 検証が無効になっていることを警告として記録します。 - 生のレスポンス本文を
/tmp/e2e_resp.jsonに保存し、ステータスコードを標準出力の最終行に出力します。
4. curl を実行してアサーションする(再試行なし)
各 curl コマンドを正確に 1 回実行します(REQ-NR008)。失敗した場合も再試行しないでください。
HTTP_CODE=$(curl -s -w "\n%{http_code}" -o /tmp/e2e_resp.json <curl args> | tail -n 1)
アサーション(すべて合格した場合にのみ、AC を VERIFIED とします):
a. HTTP ステータスコード:
if [ "$HTTP_CODE" -ne "$EXPECTED_STATUS" ]; then
echo "FAIL: Expected status $EXPECTED_STATUS, got $HTTP_CODE"
STATUS="FAILED"
fi
b. Content-Type ヘッダー(AC で指定されている場合のみ):
ACTUAL_CT=$(curl -s -o /dev/null -D - <curl args> | grep -i "Content-Type:" | head -1 | sed 's/Content-Type: //i' | tr -d '\r')
if [ -n "$EXPECTED_CT" ] && ! echo "$ACTUAL_CT" | grep -qi "$EXPECTED_CT"; then
echo "FAIL: Expected Content-Type '$EXPECTED_CT', got '$ACTUAL_CT'"
STATUS="FAILED"
fi
c. レスポンス本文の構造(jq を推奨、利用できない場合は grep を使用):
jqがインストールされていて、レスポンスが JSON の場合:# フィールドが存在することをアサート jq -e '.fieldName' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: Missing .fieldName"; STATUS="FAILED"; } # フィールドが期待値と一致することをアサート jq -e '.fieldName == "expectedValue"' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .fieldName mismatch"; STATUS="FAILED"; } # 配列の長さをアサート jq -e '(.items | length) > 0' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .items is empty"; STATUS="FAILED"; } # ネストされたフィールドを検証 jq -e '.data.user.email' /tmp/e2e_resp.json >/dev/null || { echo "FAIL: .data.user.email がありません"; STATUS="FAILED"; }jqがインストールされていない場合は、代わりにgrepを使用します。grep -q '"fieldName"' /tmp/e2e_resp.json || { echo "FAIL: fieldName がありません"; STATUS="FAILED"; }
5. 結果を記録する
各 curl テストについて、以下を記録します。
- AC ID とテキスト(省略形)
- 生成した curl コマンド(認証トークンは伏せ字にする)
- 期待されるステータスコード、Content-Type、本文の検証内容
- 実際のステータスコード、Content-Type、本文の抜粋(先頭 500 文字)
- 合否ステータス
- 実行時間(任意。レポートの参考用)
検証に失敗した場合は、直ちに AC を FAILED とします。再試行しないでください。
Web SPA テスト(Playwright)
前提条件の確認:SPA テストを生成する前に、Playwright がインストールされていることを確認します。
PLAYWRIGHT_VERSION=$(npx playwright --version 2>/dev/null || echo "")
if [ -z "$PLAYWRIGHT_VERSION" ]; then
echo "ERROR: Playwright is not installed."
echo "Install instructions:"
echo " npm install -D @playwright/test"
echo " npx playwright install chromium"
echo ""
echo "Skipping all Web SPA tests. Acceptance criteria for SPA interactions will be marked MANUAL CHECK REQUIRED."
for ac_id in $(get_spa_ac_ids); do
record_result "$ac_id" "MANUAL CHECK REQUIRED" "Playwright not installed"
done
# Continue to next test category; do not abort the whole verification
fi
echo "Playwright version: $PLAYWRIGHT_VERSION"
Playwright が見つからない場合、スキルは上記のインストールコマンドとともに不足していることを報告し、SPA テストをスキップしなければなりません。自動インストールを試みないでください。
1. アーティファクトディレクトリを準備する
ARTIFACT_DIR="${SPEC_FOLDER}/e2e-artifacts"
mkdir -p "$ARTIFACT_DIR"
2. UI の動作に関するヒントを [IMP] AC から解析する
Web SPA の動作に関連する仕様内の各 [IMP] 受け入れ基準について、以下を行います。
- UI 操作のキーワードを抽出します:
click、fill、type、select、submit、navigate、scroll、hover。 - 次のパターンを検索して、対象セレクターを抽出します。
data-testid="..."またはdata-testid='...'→[data-testid=...]id="..."またはid='...'→#...class="..."またはclass='...'→.class-name(スペースをドットに置き換える)- ボタン/リンクのテキストへの言及 →
text=... - URL パスへの言及 →
/path
- 期待される表示状態を抽出します:テキスト内容の期待値、URL の期待値、要素の有無、件数の期待値。
- フォームフィールド名と期待される入力値を抽出します。
[IMP] AC に解析可能な UI の動作または表示状態のヒントが含まれていない場合は、MANUAL CHECK REQUIRED として、次の基準に進みます。
3. ヘッドレスブラウザーコンテキストを起動する
ブラウザーはデフォルトでヘッドレスにしなければなりません。ユーザーが明示的に --headed を指定した場合に限り、ヘッドありモードを使用します。
各 SPA テストについて、一時的な Playwright スクリプトを生成し、node で実行します。
DEV_SERVER_URL="http://localhost:${TARGET_PORT}"
TEST_SCRIPT="$(mktemp /tmp/e2e-spa-XXXXXX.js)"
cat > "$TEST_SCRIPT" << 'PLAYWRIGHT_EOF'
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: 'DeveloperKit-E2E/1.0'
});
const page = await context.newPage();
// Actions and assertions injected here
await browser.close();
})();
PLAYWRIGHT_EOF
node "$TEST_SCRIPT"
4. AC を Playwright のアクションに変換する
解析した各 UI 操作について、一時スクリプト内に対応する Playwright アクションを生成します。
| AC の説明パターン | Playwright アクション |
|---|---|
| "click [selector]" | await page.click('[data-testid=refresh]'); |
| "fill [selector] with [value]" | await page.fill('#username', 'testuser'); |
| "type [value] into [selector]" | await page.type('input[name=search]', 'query'); |
| "select [value] in [selector]" | await page.selectOption('select[name=country]', 'US'); |
| "submit [form]" | await page.click('button[type=submit]'); |
| "navigate to [path]" | await page.goto('http://localhost:${TARGET_PORT}/path'); |
| "hover over [selector]" | await page.hover('.tooltip-trigger'); |
| "scroll to [selector]" | await page.locator('[data-testid=footer]').scrollIntoViewIfNeeded(); |
セレクターの優先順位(具体性の高いものから低いもの):
[data-testid=...]— 推奨。最も安定しています#id— 一意な要素 ID.class-name— CSS クラス[name=...]— フォーム要素名text=...— 表示テキストの内容(フォールバック)
5. 表示状態を検証する
期待される各表示状態について、一時スクリプト内に対応する検証を生成します。
| AC の記述パターン | Playwright のアサーション |
|---|---|
| 「ページに[テキスト]が表示される」 | await expect(page.locator('body')).toContainText('text'); |
| 「[セレクター]に[value]というテキストがある」 | await expect(page.locator('[data-testid=title]')).toHaveText('value'); |
| 「[セレクター]に[テキスト]が含まれる」 | await expect(page.locator('.message')).toContainText('text'); |
| 「テーブルに[N]行ある」 | expect(await page.locator('table tbody tr').count()).toBe(N); |
| 「URL が[path]である」 | expect(page.url()).toBe('http://localhost:${TARGET_PORT}/path'); |
| 「URL に[フラグメント]が含まれる」 | expect(page.url()).toContain('/fragment'); |
| 「[セレクター]が表示されている」 | await expect(page.locator('[data-testid=modal]')).toBeVisible(); |
| 「[セレクター]が非表示である」 | await expect(page.locator('[data-testid=spinner]')).toBeHidden(); |
6. タイムアウトを適用してテストを実行する
無期限に停止することを防ぐため、Playwright の各テストにはテストごとのタイムアウトを必ず設定する必要があります(REQ-NR007)。ラッパースクリプトでは timeout コマンドを使用します。
TEST_TIMEOUT_SEC=30
npx playwright --version >/dev/null 2>&1 || {
echo "Playwright not installed; skipping SPA tests."
record_spa_manual_check
continue
}
# Build the inline test script
TEST_SCRIPT="$(mktemp /tmp/e2e-spa-XXXXXX.js)"
cat > "$TEST_SCRIPT" << EOF
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
page.setDefaultTimeout(${TEST_TIMEOUT_SEC}000);
page.setDefaultNavigationTimeout(${TEST_TIMEOUT_SEC}000);
try {
await page.goto('${DEV_SERVER_URL}');
// --- GENERATED ACTIONS ---
// --- GENERATED ASSERTIONS ---
console.log('RESULT: PASS');
} catch (error) {
console.error('RESULT: FAIL:', error.message);
const screenshotPath = '${ARTIFACT_DIR}/screenshot-' + Date.now() + '-ac-${AC_ID}.png';
await page.screenshot({ path: screenshotPath, fullPage: true }).catch(() => {});
console.error('SCREENSHOT:', screenshotPath);
process.exitCode = 1;
} finally {
await browser.close();
}
})();
EOF
# Run with timeout wrapper; if the test hangs, it is killed and marked FAILED
if timeout --signal=TERM $((TEST_TIMEOUT_SEC + 5)) node "$TEST_SCRIPT"; then
STATUS="VERIFIED"
else
EXIT_CODE=$?
if [ "$EXIT_CODE" -eq 124 ]; then
echo "FAIL: Test hung and was terminated after ${TEST_TIMEOUT_SEC}s timeout"
fi
STATUS="FAILED"
fi
rm -f "$TEST_SCRIPT"
テストプロセスがタイムアウトを超えて停止した場合、timeout コマンドが SIGTERM を送信し、AC は FAILED としてマークされ、証跡には「テストが停止し、${TEST_TIMEOUT_SEC}s 後に終了されました」と記録されます。
7. 失敗時にスクリーンショットを撮影する
アサーションまたはアクションが失敗した場合:
page.screenshot({ path: ..., fullPage: true })を使用して、ページ全体のスクリーンショットを撮影します。${ARTIFACT_DIR}/screenshot-<timestamp>-ac-<AC_ID>.pngに保存します。- テスト結果の証跡にスクリーンショットのパスを記録します。
テストが成功した場合、スクリーンショットの撮影は任意です。--capture-success が渡された場合にのみ撮影します。
8. 結果を記録する
各 SPA テストについて、以下を記録します。
- AC ID とテキスト(省略形)
- 実行した Playwright のアクション
- 実施したアサーション
- 成功/失敗のステータス
- スクリーンショットのパス(失敗時)
- 実行時間
- エラーメッセージ(失敗時)
アサーションが失敗した場合、AC を直ちに FAILED としてマークします。再試行しないでください。
エッジケース — エラー処理:
- Playwright がインストールされていない:
npm install -D @playwright/testとnpx playwright install chromiumを提案する明確なエラーを報告します。すべての SPA AC をMANUAL CHECK REQUIREDとしてマークします。他のテストカテゴリは続行します。 - 開発サーバーが起動していない:
page.goto()がnet::ERR_CONNECTION_REFUSEDなどをスローした場合、「${DEV_SERVER_URL} で開発サーバーに接続できません。検証の前にサーバーが起動していることを確認してください。」と報告します。該当する AC をFAILEDとしてマークします。 - ブラウザーの起動に失敗した:Chromium の起動に失敗した場合(システム依存関係の不足など)、エラーを報告し、
npx playwright install-deps chromiumを提案します。該当する AC をMANUAL CHECK REQUIREDとしてマークします。
デスクトップテスト(コンピューター操作/MCP)
前提条件の確認:デスクトップテストを生成する前に、コンピューター操作または MCP ベースの GUI 自動化ツールが利用可能であることを確認します。このスキルは、これらのツールを自動インストールしません。
GUI_TOOLS_AVAILABLE=false
GUI_TOOL_NAME=""
if [ -n "$CLAUDE_COMPUTER_USE_AVAILABLE" ] || command -v computer-use >/dev/null 2>&1; then
GUI_TOOLS_AVAILABLE=true
GUI_TOOL_NAME="computer-use"
fi
if [ -n "$MCP_GUI_SERVER_URL" ] || command -v mcp-gui >/dev/null 2>&1; then
GUI_TOOLS_AVAILABLE=true
GUI_TOOL_NAME="mcp-gui"
fi
if [ "$GUI_TOOLS_AVAILABLE" != "true" ]; then
echo "ERROR: Desktop testing tools are not available."
echo "This skill requires computer-use or MCP GUI automation tools to verify desktop applications."
echo ""
echo "To enable desktop verification:"
echo " - Run in an environment with computer-use support, OR"
echo " - Install and configure an MCP GUI automation server"
echo ""
echo "Aborting desktop verification. No tests will be executed."
exit 1
fi
echo "Desktop GUI tool detected: $GUI_TOOL_NAME"
1. 成果物ディレクトリを準備する
ARTIFACT_DIR="${SPEC_FOLDER}/e2e-artifacts"
mkdir -p "$ARTIFACT_DIR"
2. [IMP] AC からデスクトップの動作に関するヒントを解析する
仕様内でデスクトップアプリの動作に関連する各 [IMP] 受け入れ条件について:
- ウィンドウタイトルのヒントを抽出します:「ウィンドウが開く」「ダイアログが表示される」「モーダルに表示される」「タイトルに含まれる」「画面に表示される」などの表現を探します。
- UI 要素の説明を抽出します:ボタンのラベル、入力欄のプレースホルダー、メニュー項目、チェックボックスのラベル、トグル名を探します。
- ワークフローの手順を抽出します:「…をクリックしてから…」「…を選択して…を押す」「…を切り替える」などの操作手順を探します。
- 期待される状態変化を抽出します:「テーマがダークに変わる」「ステータスインジケーターが緑色になる」「リストが更新される」などの視覚的な変化を探します。
[IMP] AC に解析可能なデスクトップ動作のヒント(ウィンドウ、要素、ワークフローの説明)が含まれていない場合は、MANUAL CHECK REQUIRED としてマークし、次の条件に進みます。
3. アプリケーションが実行中であることを確認する
Phase 3 でデスクトップアプリが起動されていない場合、またはそのプロセスが終了している場合は、Phase 3 のコマンドを使用して再起動します。
if [ -n "$STARTUP_PID" ] && ! kill -0 "$STARTUP_PID" 2>/dev/null; then
echo "Desktop app process is not running. Re-launching..."
# Re-run the appropriate launch command from Phase 3 based on DESKTOP_FRAMEWORK
fi
4. ウィンドウ/UI 要素を視覚検査またはアクセシビリティツリー検査で確認する
ウィンドウまたは UI 要素について記述している各 AC(AC-022)に対して、次のように確認します。
-
視覚検査(コンピューター操作):
- デスクトップのスクリーンショットを撮影します。
- 画像を分析し、期待されるウィンドウタイトル、ボタンラベル、または視覚要素を確認します。
- 例:「Settings ウィンドウが開く」→ スクリーンショットを撮影し、「Settings」というタイトルのウィンドウが表示されていることを確認します。
-
アクセシビリティツリー検査(MCP GUI 自動化):
- アクセシビリティツリーを照会し、期待される名前、ロール、または状態に一致する要素を検索します。
- 例:「Settings ウィンドウが開く」→ 名前に「Settings」を含むウィンドウ要素を照会します。
- 例:「ダークモードを切り替える」→ ラベルに「Dark mode」を含むスイッチ/チェックボックス要素を照会します。
5. GUI 自動化でユーザーワークフローをシミュレートする
ユーザーワークフローについて記述している各 AC(AC-023)に対して、説明を次の GUI 自動化アクションに変換します。
| AC の説明パターン | GUI 自動化アクション |
|---|---|
| 「[ボタンラベル]をクリックする」 | 一致するアクセシブル名またはラベルを持つ UI 要素をクリックします |
| 「[フィールド]に[値]を入力する」 | 入力フィールドにフォーカスし、値を入力します |
| 「[スイッチ/チェックボックス]を切り替える」 | スイッチまたはチェックボックス要素をクリックします |
| 「ドロップダウンから[オプション]を選択する」 | ドロップダウンを開き、オプション要素をクリックします |
| 「[メニュー項目]に移動する」 | 一致するラベルのメニュー項目をクリックします |
| 「[フィールド]に[値]を入力する」 | フィールドにフォーカスし、値を入力します |
6. 各検証ステップでスクリーンショットを撮影する
各ステップ(アクションの前、アクションの後、アサーション時)で、必ずスクリーンショットを撮影します。
SCREENSHOT_PATH="${ARTIFACT_DIR}/screenshot-$(date +%s)-ac-${AC_ID}-step-${STEP_NUM}.png"
# computer-use: capture screenshot and save to SCREENSHOT_PATH
# MCP: use the GUI automation server's screenshot capability
合否にかかわらず、すべてのステップでスクリーンショットが保存され、AC-024(SEF)を満たします。
7. テストごとのタイムアウトを適用する
無期限のハングを防ぐため、各デスクトップテストには必ずテストごとのタイムアウトを適用します(REQ-NR007)。
TEST_TIMEOUT_SEC=60
TEST_START_TIME=$(date +%s)
TEST_STATUS="RUNNING"
while [ "$TEST_STATUS" = "RUNNING" ]; do
if [ $(( $(date +%s) - TEST_START_TIME )) -gt "$TEST_TIMEOUT_SEC" ]; then
echo "FAIL: Desktop test hung and was terminated after ${TEST_TIMEOUT_SEC}s timeout"
STATUS="FAILED"
TEST_STATUS="TIMEOUT"
FINAL_SCREENSHOT="${ARTIFACT_DIR}/screenshot-$(date +%s)-ac-${AC_ID}-timeout.png"
# capture screenshot to FINAL_SCREENSHOT
break
fi
# Execute next workflow step; if all steps complete, set TEST_STATUS="COMPLETED"
done
テストがタイムアウトした場合:
- AC を
FAILEDとしてマークします - 最終タイムアウト時のスクリーンショットを撮影します
- 証拠として「テストがハングしたため、${TEST_TIMEOUT_SEC}秒のタイムアウト後に終了しました」と記録します
- 次の AC の前に、アプリケーションの状態をリセットする(ウィンドウを閉じて再度開く)ことを試みます
8. 結果を記録する
各デスクトップテストについて、次の内容を記録します。
- AC ID とテキスト(省略形)
- 実行した GUI 自動化アクション(ステップ一覧)
- 撮影したスクリーンショット(ステップの説明を含むパス一覧)
- 合否
- エラーメッセージ、タイムアウトの理由、またはアサーションの不一致(失敗時)
アサーションまたはアクションが失敗した場合は、直ちに AC を FAILED としてマークします。再試行しないでください。
エッジケース — エラー処理:
- コンピューター操作ツールまたは MCP ツールが利用できない:明確なエラーメッセージを表示して検証を中止します。デスクトップテストを黙って省略してはいけません。
- ビルド失敗:ビルド出力は
$STARTUP_LOGS_FILEに保存され、報告されます。起動前に検証を中止します。 - テスト中のアプリのクラッシュ:テスト中にデスクトッププロセスが終了した場合は、ログからクラッシュ出力を取得し、現在の AC を
FAILEDとしてマークして、次の AC に向けてアプリの再起動を試みます。 - ウィンドウ/要素が見つからない:期待されるウィンドウまたは要素がテストのタイムアウトまでに表示されない場合は、AC を
FAILEDとしてマークし、現在のデスクトップ状態のスクリーンショットを撮影します。
フォールバック:人間による美的判断が必要な場合など、自動テストに変換できない特定の AC は、MANUAL CHECK REQUIRED としてマークし、続行します。
フェーズ 5:結果を受け入れ基準に対応付ける
目標:すべてのテスト実行結果を機能仕様の [IMP] 受け入れ基準と照合し、それぞれについて判定結果を確定します。
5.1 結果テーブルの初期化
AC ID をキーとする連想型の結果構造(例:シェルの連想配列、JSON オブジェクト、一時ファイル)を作成します。フェーズ 1 で抽出した [IMP] 受け入れ基準ごとに、次の内容で行を事前に作成します。
| フィールド | 初期値 |
|---|---|
ac_id | AC 識別子(例:AC-012) |
ac_text | 基準の全文。表示用に 120 文字で切り捨てます |
status | PENDING(5.2 で更新) |
evidence | 空文字列(5.2 で更新) |
actual | 空文字列(不一致時のみ入力) |
expected | 空文字列(不一致時のみ入力) |
仕様書の受け入れ基準テーブルを確認し、分類タグが [IMP] の行のみを含めます。[SEF] および [EXT] の基準は実行時検証の対象外です。付録に記載してもかまいませんが、ステータス判定は不要です。
5.2 判定ルールの適用
フェーズ 4 でテスト対象となった各 AC に対し、次のルールを順番に適用します。
-
VERIFIED(REQ-016):
- テストがアサーション失敗、タイムアウト、ツールエラーなしで完了した場合
status = "VERIFIED"に設定しますevidenceに簡潔な説明を設定します。- REST API:
curl returned HTTP <code> in <N>ms - Web SPA:
Playwright assertion passed: <selector> <condition> - デスクトップ:
GUI automation confirmed: <window/element> present
- REST API:
-
FAILED(REQ-018):
- アサーションが失敗した、テストがタイムアウトした、ツールがクラッシュした、または開発サーバーに接続できなかった場合
status = "FAILED"に設定しますactualに観測値を設定します(例:HTTP 400、element not found、timeout after 30s)expectedに AC が要求する値を設定します(例:HTTP 201、element visible)evidenceに、actualとexpectedを比較した人が読める文章を設定し、利用可能な場合は取得した成果物(スクリーンショット、レスポンスダンプ)のパスも含めます。
-
MANUAL CHECK REQUIRED(REQ-017):
- 次の理由により AC のテストが生成されなかった場合:
- AC のテキストに、解析可能なエンドポイント/UI/デスクトップ動作の手がかりがなかった
- 必要なツールが不足していた(例:Playwright がインストールされていない)
- AC が人による判断を記述している(例:「UI が正しく見える」)
status = "MANUAL CHECK REQUIRED"に設定しますevidenceに理由を設定します:No automated test could be derived: <reason>
- 次の理由により AC のテストが生成されなかった場合:
重要:すべての [IMP] AC は、最終ステータスを VERIFIED、FAILED、または MANUAL CHECK REQUIRED のいずれかにする必要があります。フェーズ 5 の終了時に PENDING のままの AC があってはなりません。
5.3 サマリー数の算出
すべての行に値を設定したら、次を算出します。
TOTAL_IMP=$(count_implementation_acs)
VERIFIED_COUNT=$(grep -c '"status":"VERIFIED"' "$RESULTS_FILE")
FAILED_COUNT=$(grep -c '"status":"FAILED"' "$RESULTS_FILE")
MANUAL_COUNT=$(grep -c '"status":"MANUAL CHECK REQUIRED"' "$RESULTS_FILE")
これらの件数を保存します。レポートのサマリーセクション(REQ-020)で必要になります。
フェーズ 6:レポートの生成
目標:フェーズ 6.3 で定義された形式に従い、秘密情報を含まない、決定性のある読みやすい Markdown レポートを作成します。
6.1 レポートのパスとディレクトリの準備
REPORT_TIMESTAMP=$(date +%Y-%m-%d-%H%M%S)
REPORT_DIR="${SPEC_FOLDER}"
ARTIFACT_DIR="${REPORT_DIR}/e2e-artifacts"
REPORT_FILE="${REPORT_DIR}/e2e-report-${REPORT_TIMESTAMP}.md"
mkdir -p "$ARTIFACT_DIR"
SPEC_FOLDERは--spec引数の値です(例:docs/specs/001-real-e2e-verification/)。- レポートのファイル名は、正確なパターン
e2e-report-YYYY-MM-DD-HHMMSS.mdを使用する必要があります(AC-026)。 - 成果物のパスをレポートに記載する前に、成果物ディレクトリが存在している必要があります。
6.2 生の出力から秘密情報を伏せる
レポートにコマンド出力を書き込む前に、生ログに対して情報を伏せる処理を実行します(REQ-NR002)。
# Redact Authorization header values
sed -E 's/(Authorization:[[:space:]]*[Bb]earer[[:space:]]+)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE" > "$STARTUP_LOGS_FILE.redacted"
# Redact tokens in JSON bodies
sed -E 's/("token"[[:space:]]*:[[:space:]]*")[^"]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted" > "$STARTUP_LOGS_FILE.redacted2"
# Redact passwords in curl -u arguments
sed -E 's/(-u[[:space:]]+[^:]*:)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted2" > "$STARTUP_LOGS_FILE.redacted3"
# Redact E2E_* environment variables (REQ-NR002)
sed -E 's/(E2E_[A-Z_]+=)[^[:space:]]+/\1***REDACTED***/g' "$STARTUP_LOGS_FILE.redacted3" > "$STARTUP_LOGS_FILE.redacted_final"
レポートには、伏せ字処理済みのバージョン($STARTUP_LOGS_FILE.redacted_final)のみを含めます。元の一時ファイルは破棄します。
6.3 レポートの各セクションを作成する
以下の正確な順序で各セクションを追記して、レポートを作成します。レポート内のすべてのパスは、レポートファイルの場所からの相対パスでなければなりません。
セクション 1 — 概要
## Summary
| Metric | Count |
|--------|-------|
| Total AC Evaluated | ${TOTAL_IMP} |
| ✅ VERIFIED | ${VERIFIED_COUNT} |
| ❌ FAILED | ${FAILED_COUNT} |
| ⚠️ MANUAL CHECK REQUIRED | ${MANUAL_COUNT} |
セクション 2 — 環境
## Environment
| Property | Value |
|----------|-------|
| Application Type | ${APP_TYPE} |
| Startup Command | \`${STARTUP_COMMAND}\` |
| Target Port | ${TARGET_PORT} |
| Health Endpoint | ${HEALTH_CHECK_METHOD} |
| Runtime Version | ${RUNTIME_VERSION} |
| Verification Started | ${ISO8601_START_TIME} |
RUNTIME_VERSION:起動時に検出されたランタイムのバージョンを記録します。- Docker:
docker --version - Node.js:
node --version - Java:
java -version 2>&1 | head -1 - Python:
python --versionまたはpython3 --version
- Docker:
ISO8601_START_TIME:Phase 3 の開始時刻を ISO-8601 形式で記録します。
セクション 3 — テスト結果
## Test Results
| AC ID | Criterion (truncated) | Status | Evidence |
|-------|----------------------|--------|----------|
| AC-012 | WHEN the app is classified... | VERIFIED | \`curl\` returned 200 |
| AC-013 | WHEN a curl test executes... | FAILED | Expected 201, got 400 |
| AC-014 | WHEN authentication is required... | MANUAL CHECK REQUIRED | No credentials found |
- 「Criterion (truncated)」列は 120 文字に切り詰め、120 文字を超える場合は
...を追加しなければなりません。 - 「Status」列は
VERIFIED、FAILED、MANUAL CHECK REQUIREDのいずれかでなければなりません。 - 「Evidence」列では、該当する場合、相対パスを使って成果物を参照しなければなりません(例:
./e2e-artifacts/ac-019-screenshot.png)。
セクション 4 — 生の出力
## Raw Output
### Startup Logs
\`\`\`
[contents of $STARTUP_LOGS_FILE.redacted_final]
\`\`\`
### Test Commands
\`\`\`
[For each test: the exact command executed (auth redacted) and its first 2000 chars of stdout/stderr]
\`\`\`
- テストで出力がなかった場合は、
(no output)と記載します。 - レポートが過度に長くならないよう、各コードブロックは 2000 行までとします。ログがそれより長い場合は、次の注記を追記します:
(truncated; full logs available in <path>)
セクション 5 — 成果物
## Artifacts
| AC ID | Type | Path |
|-------|------|------|
| AC-019 | Screenshot | ./e2e-artifacts/screenshot-1717189200-ac-019.png |
| AC-014 | Response Dump | ./e2e-artifacts/ac-014-response.json |
$ARTIFACT_DIRにある成果物ファイルをすべて列挙します。- 成果物が生成されなかった場合は、
No artifacts captured for this run.と記載します。
セクション 6 — ティアダウンのステータス
## Teardown Status
| Property | Value |
|----------|-------|
| Teardown Executed | ${TEARDOWN_EXECUTED} |
| Port Released | ${PORT_RELEASED} |
| Remaining Processes | ${REMAINING_PROCESSES} |
TEARDOWN_EXECUTED:true/false/skipped (--keep-alive)PORT_RELEASED:true/false/unknownREMAINING_PROCESSES:ティアダウン後も稼働している PID またはコンテナー名をカンマ区切りで列挙します。該当するものがない場合はnoneとします。
6.4 レポートを確定して保存する
cat > "$REPORT_FILE" << 'REPORT_EOF'
[all sections assembled above]
REPORT_EOF
echo "E2E report saved to: $REPORT_FILE"
- レポートは有効な Markdown でなければなりません(基本的な
markdownlintルールに準拠すること)。見出しレベルを適切にし、行末の空白をなくし、パイプで区切る表の区切り文字を統一します。 - レポートを書き込んだ後は、変更してはなりません(REQ-NR005)。
- Phase 8 で使用するため、変数
REPORT_PATH="$REPORT_FILE"を設定します。
Phase 7:環境をティアダウンする
目標:Phase 3 で起動したすべてのランタイムリソースを正常に停止し、ポートとプロセスが完全に解放されたことを確認して、その結果を記録します。--keep-alive が渡された場合は、ティアダウンを完全にスキップしてユーザーに警告します。
7.1 --keep-alive ガード
if [ "$KEEP_ALIVE" = "true" ]; then
TEARDOWN_EXECUTED="skipped (--keep-alive)"
PORT_RELEASED="skipped"
REMAINING_PROCESSES="skipped"
echo "WARNING: --keep-alive was passed. The runtime environment is still running."
echo "You are responsible for cleaning up:"
if [ "$APP_TYPE" = "Docker-managed" ]; then
echo " docker compose -f '${PROJECT_ROOT}/docker-compose.yml' down"
else
echo " kill $(lsof -ti:${TARGET_PORT})"
fi
echo "Report was saved before teardown; artifacts are in ${ARTIFACT_DIR}."
# Skip remaining teardown steps and proceed to Phase 8
# (Phase 7 cleanup is bypassed when --keep-alive is active)
fi
7.2 アプリケーションの種類ごとに正常にティアダウンする
Docker 管理対象(AC-029):
TEARDOWN_EXECUTED="true"
DOCKER_TEARDOWN_FAILED=false
if [ "$APP_TYPE" = "Docker-managed" ]; then
cd "$PROJECT_ROOT"
if docker compose down >> "$STARTUP_LOGS_FILE" 2>&1; then
echo "Docker Compose stack stopped successfully."
else
echo "WARNING: docker compose down exited with a non-zero code."
DOCKER_TEARDOWN_FAILED=true
fi
fi
ローカルプロセス(JVM、Node.js/NestJS、Python、デスクトップ)(AC-029):
if [ -n "$STARTUP_PID" ] && kill -0 "$STARTUP_PID" 2>/dev/null; then
echo "Sending SIGTERM to process $STARTUP_PID ..."
kill -TERM "$STARTUP_PID" 2>/dev/null || true
# Wait up to 10 seconds for graceful exit
GRACEFUL_WAIT=0
while [ "$GRACEFUL_WAIT" -lt 10 ] && kill -0 "$STARTUP_PID" 2>/dev/null; do
sleep 1
GRACEFUL_WAIT=$((GRACEFUL_WAIT + 1))
done
# If still alive, send SIGKILL
if kill -0 "$STARTUP_PID" 2>/dev/null; then
echo "Process $STARTUP_PID did not exit after 10s; sending SIGKILL."
kill -KILL "$STARTUP_PID" 2>/dev/null || true
sleep 2
fi
fi
# Also terminate any child processes that may still hold the port
PORT_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null || true)
if [ -n "$PORT_PIDS" ]; then
echo "Additional PIDs holding port $TARGET_PORT: $PORT_PIDS"
for pid in $PORT_PIDS; do
kill -TERM "$pid" 2>/dev/null || true
done
sleep 3
for pid in $PORT_PIDS; do
if kill -0 "$pid" 2>/dev/null; then
kill -KILL "$pid" 2>/dev/null || true
fi
done
fi
緊急クリーンアップ — クラッシュしたテストの孤立プロセス:
# If the original STARTUP_PID is empty or already dead, but the port is still occupied,
# this indicates an orphan process from a crash or a detached child.
if [ -z "$STARTUP_PID" ] || ! kill -0 "$STARTUP_PID" 2>/dev/null; then
ORPHAN_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null || true)
if [ -n "$ORPHAN_PIDS" ]; then
echo "WARNING: Orphan process(es) detected on port $TARGET_PORT: $ORPHAN_PIDS"
for pid in $ORPHAN_PIDS; do
kill -TERM "$pid" 2>/dev/null || true
done
sleep 3
for pid in $ORPHAN_PIDS; do
if kill -0 "$pid" 2>/dev/null; then
kill -KILL "$pid" 2>/dev/null || true
fi
done
fi
fi
7.3 ポートの解放を確認する(AC-030)
PORT_RELEASED="false"
PORT_CHECK_WAIT=0
MAX_PORT_CHECK_WAIT=15
while [ "$PORT_CHECK_WAIT" -lt "$MAX_PORT_CHECK_WAIT" ]; do
if ! lsof -i :"$TARGET_PORT" >/dev/null 2>&1 && ! nc -z localhost "$TARGET_PORT" 2>/dev/null; then
PORT_RELEASED="true"
echo "Port $TARGET_PORT is free."
break
fi
sleep 1
PORT_CHECK_WAIT=$((PORT_CHECK_WAIT + 1))
done
if [ "$PORT_RELEASED" != "true" ]; then
echo "WARNING: Port $TARGET_PORT is still occupied after teardown."
fi
7.4 残存リソースを特定する(REQ-023)
REMAINING_PROCESSES="none"
if [ "$APP_TYPE" = "Docker-managed" ]; then
REMAINING_CONTAINERS=$(docker compose ps --format '{{.Name}}' 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [ -n "$REMAINING_CONTAINERS" ]; then
REMAINING_PROCESSES="containers: $REMAINING_CONTAINERS"
fi
else
REMAINING_PIDS=$(lsof -ti:"$TARGET_PORT" 2>/dev/null | tr '\n' ' ' | sed 's/ $//')
if [ -n "$REMAINING_PIDS" ]; then
REMAINING_PROCESSES="PIDs: $REMAINING_PIDS"
fi
fi
7.5 ティアダウンが不完全な場合にユーザーへ警告する
if [ "$PORT_RELEASED" != "true" ] || [ "$DOCKER_TEARDOWN_FAILED" = "true" ]; then
echo "WARNING: Teardown did not complete cleanly. Manual cleanup may be required."
if [ "$APP_TYPE" = "Docker-managed" ]; then
echo " Remaining containers: ${REMAINING_PROCESSES}"
echo " Manual command: docker compose -f '${PROJECT_ROOT}/docker-compose.yml' down"
else
echo " Remaining PIDs on port ${TARGET_PORT}: ${REMAINING_PIDS}"
echo " Manual command: kill ${REMAINING_PIDS}"
fi
fi
7.6 ティアダウンのステータスを記録する
レポートファイルをその場で更新します(Phase 6 でティアダウンのステータスが書き込まれていない場合はそのセクションを追記するか、Phase 6 でレポートを確定する前に変数が正しく設定されていることを確認します)。推奨される順序は次のとおりです。
- Phase 6 で、ティアダウンの変数をプレースホルダーとしてレポートに書き込みます。
- Phase 7 でティアダウンを実行します。
- Phase 7 で、インプレース編集(例:
sed)を使ってプレースホルダーを実際の値に更新します。
sed -i.bak \
-e "s/\${TEARDOWN_EXECUTED}/${TEARDOWN_EXECUTED}/g" \
-e "s/\${PORT_RELEASED}/${PORT_RELEASED}/g" \
-e "s/\${REMAINING_PROCESSES}/${REMAINING_PROCESSES}/g" \
"$REPORT_FILE" && rm -f "$REPORT_FILE.bak"
ティアダウン前にレポートがすでに確定している場合(有効な代替手順)は、レポートの末尾に ティアダウン後の更新 セクションを追記します。
## Post-Teardown Update
| Property | Value |
|----------|-------|
| Teardown Executed | ${TEARDOWN_EXECUTED} |
| Port Released | ${PORT_RELEASED} |
| Remaining Processes | ${REMAINING_PROCESSES} |
重要:ティアダウン中にレポートを削除したり、仕様ファイルやタスクファイルを変更したりしてはなりません(REQ-NR005)。
Phase 8:結果を提示する
- 簡潔な概要をユーザーに表示します。
- ✅ 検証済み:N | ❌ 失敗:N | ⚠️ 手動確認:N
- レポートのパス
- ティアダウンのステータス(または
--keep-aliveの通知)
- 失敗した AC がある場合:
- 失敗した AC と証拠を強調表示します。
- 失敗したタスクに対して
/developer-kit-specs:specs.task-implementationを再実行するよう推奨します。
- すべての ToDo を完了済みにします。
例
Spring Boot REST API の検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/001-user-auth/
アクション:
pom.xmlとdocker-compose.ymlを検出 → Docker 管理の Spring Bootdocker compose up -dを実行localhost:8080/actuator/healthを待機- 仕様書から AC を読み取り、ログインエンドポイントを記述した
[IMP]AC を見つける - 次を実行:
curl -s -w "\n%{http_code}" -X POST http://localhost:8080/api/login -d '{"email":"[email protected]","password":"secret"}' - ステータスが
200で、レスポンスにtokenが含まれることを確認 - レポートを生成:
docs/specs/001-user-auth/e2e-report-2026-05-31-143022.md docker compose downを実行
React SPA の検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/002-dashboard/
アクション:
react依存関係を含むpackage.jsonを検出npm run devを実行し、localhost:5173を待機- Playwright を起動し、
/dashboardに移動 - AC に「ユーザーは[更新]をクリックしてデータを再読み込みできます」とある場合 →
page.click('[data-testid=refresh]')を実行し、テーブルの行が更新されることを確認 - 成功時にスクリーンショットを撮影し、失敗時にキャプチャを取得
- スクリーンショットの成果物を含めてレポートを生成
- 開発サーバーのプロセスを終了
デスクトップアプリの検証
/developer-kit-specs:specs.e2e-verify --spec=docs/specs/003-settings-app/
アクション:
src-tauri/Cargo.tomlを検出 → Tauri デスクトップアプリcargo tauri build --debugを実行し、.app/.exeを起動- コンピューター操作を使って、設定ウィンドウが開くことを確認
- AC に「ユーザーはダークモードを切り替えられます」とある場合 → 切り替えをクリックしてスクリーンショットを撮影し、テーマが変わることを確認
- 各手順のスクリーンショットを含めてレポートを生成
- アプリを閉じ、プロセスが終了したことを確認
コマンドの許可リスト
references/test-execution-patterns.md に記載されたコマンドのみ、自動実行できます。許可リストの対象は次のとおりです。
| カテゴリ | 許可されるコマンド |
|---|---|
| Docker | docker compose up -d --build、docker compose down、docker compose ps、docker compose logs |
| JVM/Spring Boot | ./mvnw spring-boot:run、./gradlew bootRun |
| Node.js/NestJS | npm run dev、npm run start:dev、npm start、node server.js |
| Python | uvicorn main:app --reload、python manage.py runserver、flask run |
| デスクトップ(Tauri) | cargo tauri build --debug、cargo tauri dev、open *.app、バイナリの直接実行 |
| デスクトップ(Electron) | npm run electron:dev、npx electron . |
| テスト | curl(--insecure が渡された場合を除き、-k/--insecure を付けない)、npx playwright、node(インラインの Playwright スクリプト用) |
| ティアダウン | docker compose down、kill -TERM、kill -KILL(停止したプロセスのみ)、lsof、nc |
許可リスト外のコマンドに関するポリシー:導出されたコマンドが許可リストにない場合は、実行前に AskUserQuestion を使って、ユーザーに明示的な確認を求めます。ユーザーの承認が必要です。承認されない場合は中止します。
セキュリティガードと禁止要件
次の禁止要件(REQ-NR001~REQ-NR008)は、スキルのワークフロー内の複数の段階で適用されます。
REQ-NR001:破壊的なコマンドの禁止
システムは、起動、テスト、ティアダウンの実行中に、sudo、rm -rf、docker system prune、mkfs、dd、またはその他の破壊的なシステムコマンドを実行してはなりません。
適用方法:
- フェーズ 1.5 では、実行前にすべてのコマンドを走査し、禁止パターンがないか確認します。
- フェーズ 3 のデータ整合性に関する事前チェックでは、データを破壊するコマンドを拒否します。
- ティアダウンコマンドは、
docker compose downと正常なプロセス終了(停止した孤立プロセスに対するkill -TERM/kill -KILLのみ)に制限されます。
REQ-NR002:レポートにシークレットを含めない
システムは、E2E レポートにシークレット、API キー、またはパスワードを含めてはなりません。
適用方法:
- フェーズ 6 のマスキング処理では、次の内容を削除します。
Authorization: Bearer <token>→Authorization: Bearer ***REDACTED***E2E_AUTH_TOKEN=...、E2E_USERNAME=...、E2E_PASSWORD=...→E2E_xxx=***REDACTED***"token": "..."→"token": "***REDACTED***"curl -u user:password内のパスワード →curl -u user:***REDACTED***
- レポートには、マスキング済みのログのみが書き込まれます。
- マスキング直後に、マスキング前の一時ファイルを破棄します。
REQ-NR003: デフォルトで TLS をバイパスしない
システムは、デフォルトで curl の TLS 証明書検証(-k/--insecure)を無効にしてはなりません。
適用:
- Phase 1.5 では、
--insecureフラグが明示的に渡されていない限り、-kまたは--insecureを含む curl コマンドを拒否します。 - Phase 4 の curl コマンド生成ルールでは、
-k/--insecureの追加を明示的に禁止します。 --insecureが渡された場合 → 次の警告をログに記録します:「TLS 証明書の検証が無効になっています。ローカル開発でのみ使用してください。」
REQ-NR004: 起動時にデータを上書きしない
システムは、起動コマンド自体が既存のデータベース、ボリューム、またはローカルデータを上書きまたは削除する場合を除き、それらを上書きまたは削除してはなりません。
適用:
- Phase 1.5 のデータ整合性事前チェックでは、
rm、--volumes、prune、drop、または同様の破壊的なデータ操作パターンを含むコマンドを拒否します。 - Phase 3 のデータ整合性事前チェックでは、コマンドによって既存データが破壊される場合、起動を中止します。
REQ-NR005: 仕様ファイルやタスクファイルを変更しない
システムは、レポートの生成中またはクリーンアップ中に、機能仕様やタスクファイルを変更してはなりません。
適用:
- Phase 6 では、レポートを新しいファイル(
e2e-report-YYYY-MM-DD-HHMMSS.md)としてのみ書き込みます。 - Phase 7 のクリーンアップでは、レポートに「クリーンアップ後の更新」セクションのみを追記し、仕様ファイルやタスクファイルは変更しません。
- このスキルは、ソースコード、仕様、タスクに対して読み取り専用です。
REQ-NR006: 失敗時に孤立プロセスを残さない
システムは、起動に失敗した場合、孤立したプロセスやコンテナーを実行中のまま残してはなりません。
適用:
- Phase 3 の Docker 起動では、タイムアウト時に終了する前に
docker compose downを実行します。 - Phase 3 の Spring Boot/Node.js 起動では、タイムアウト時に終了する前に
kill -TERM $STARTUP_PIDを実行します。 - Phase 7 のクリーンアップでは、対象ポートをまだ使用しているプロセスを緊急クリーンアップします。
REQ-NR007: テストごとのタイムアウトを適用する
システムは、停止したテストによって無期限にブロックされてはなりません。
適用:
- Phase 4 の Playwright テストは
timeout --signal=TERM <N>でラップします。タイムアウトした場合、AC をFAILEDとします。 - Phase 4 のデスクトップテストでは、手動のポーリングループで経過時間を
TEST_TIMEOUT_SECと比較します。制限時間を超えた場合、AC をFAILEDとします。 - テストが停止した場合は終了させて
FAILEDとし、VERIFIEDまたはPENDINGにはしません。
REQ-NR008: 失敗した curl テストを再試行しない
システムは、失敗した curl リクエストを再試行してはなりません。
適用:
- Phase 4 の curl 実行では、各コマンドを正確に 1 回だけ実行します。
--retryフラグ、再試行ループ、フォールバックによる再実行は一切行いません。- 最初の結果を直ちに記録し、失敗した場合は AC を
FAILEDとします。
制約と警告
- ソースは読み取り専用:このスキルは、アプリケーションのソースコード、テストファイル、設定、機能仕様、タスクファイルを編集しません。
- 自動インストールなし:Playwright または Docker が見つからない場合、このスキルは不足を報告し、インストールコマンドを提案します。
npm install -gやbrew installを自動的に実行することはありません。 - ローカルのみ:テストは
localhostに対して実行します。リモート URL、ステージング環境、本番環境のエンドポイントは対象外です。 - 破壊的操作のガード:起動、テスト、クリーンアップのコマンドは
references/test-execution-patterns.mdに従ってホワイトリストに登録されています。ホワイトリストにないコマンドには、AskUserQuestionによるユーザー確認が必要です。 - シークレットの取り扱い:認証ヘッダー、ベアラートークン、
E2E_*環境変数、パスワードはレポート内でマスキングされます。表示されるのはヘッダー名とマスキング済みの値のみです。 - TLS のデフォルト設定:curl コマンドはデフォルトで厳格な TLS 検証を行います。自己署名証明書を使ったローカル開発の場合に限り、
--insecureを渡してください。 - クリーンアップの責任:
--keep-aliveが渡された場合、手動でのクリーンアップはユーザーの責任となります。 - 永続的なテストスイートなし:このスキルは検証用の一時的なテストを生成します。プロジェクト内に永続的なテストファイルを作成することはありません。