test-site について
test-siteは、デプロイして有効化したPower Pagesサイトを実行時にテストするスキルです。ブラウザーでページ、認証状態、Web APIリクエストを確認し、応答形式を記録して分類レポートを作成します。ファイルやデータは変更しません。
主な機能
- 01ホームページの読み込みを確認
- 02最大25ページをクロール
- 03ブラウザーのコンソールエラーを記録
- 04Web APIリクエストの状態を検証
- 05サーバーロジックの応答形式を取得
- 06分類別テストレポートを生成
活用シーン
- 01デプロイ後にページの表示可否を確認
- 02失敗した/_api/リクエストを調査
- 03API応答形式とフロントエンドの想定を照合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill test-siteインストール後、Claude Code で/test-siteを入力して読み込まれたか確認します。
詳細
プラグインの確認:
node "${PLUGIN_ROOT}/scripts/check-version.js"を実行します。メッセージが出力された場合は、続行する前にユーザーに表示してください。
Power Pages サイトのテスト
デプロイして有効化した Power Pages サイトを実行時にテストします。ブラウザーでサイトを閲覧し、検出可能なすべてのリンクをクロールして、ページが正しく読み込まれることを確認します。ネットワークトラフィックを取得して API リクエストをテストし、包括的なテストレポートを生成します。
前提条件: このスキルでは、デプロイして有効化済みの Power Pages サイトが必要です。サイトがまだ公開されていない場合は、最初に
/deploy-siteと/activate-siteを実行してください。
基本原則
- 非破壊的: このスキルは読み取り専用です。ファイルやデータの作成、変更、削除は行いません。ブラウザーを通じてサイトを観察するだけです。
- API ファーストのテスト: ページの読み込み確認に加えて、主な目的はすべての
/_api/(Web API / OData)リクエストが成功応答を返すことを確認することです。 - レスポンス形式の検出:
/_api/serverlogics/エンドポイントについては、フロントエンド連携を推測ではなく実際のレスポンスに基づいて記述できるよう、テスト実行時に実際のレスポンスボディの形式も取得して報告する必要があります。フロントエンドの解析やフィールドアクセスが観測された形式と一致しない場合は、その不一致を報告し、必要な解析またはフィールドアクセスの変更について説明してください。このスキルはコードを変更しません。 - ユーザーが制御する認証: 自動でログインしようとしないでください。認証が必要な場合は、必ずブラウザーウィンドウからログインするようユーザーに依頼してください。
- 上限付きクロール: 動的 URL やページ分割された URL による無限ループを防ぐため、ページのクロールは最大 25 ページに制限してください。
検証テストのカテゴリ
実行ごとに、カテゴリ分けされたテストレポート(docs/alm/last-test-site.json — Phase 6.7a を参照)が生成されます。各カテゴリの安定した ID と、そのカテゴリを生成するフェーズは次のとおりです。
カテゴリ id | 表示名 | ソースフェーズ | 対象範囲 |
|---|---|---|---|
site-load | サイトの読み込み | Phase 2 | ホームページの HTTP ステータス、リダイレクト処理、初期レンダリング。ホームページにつきカード 1 件。失敗は重大です。 |
authentication | 認証 | Phase 3 | 匿名状態から Entra へのリダイレクト、プライベートサイトのゲート検出、ログインフローの整合性。プライベートサイトでは重大です。 |
page-crawl | ページのクロール | Phase 4 | テストしたページごとにカード 1 件(最大 25 件)。各カードにはページ URL、HTTP ステータス、コンソールエラーがあればその内容が含まれます。重大度は HTTP クラスに応じて変わります(5xx → 重大、公開サイトの 4xx → 高)。 |
web-api | Web API | Phase 5 | 実行中に観測された /_api/ エンドポイントごとにカード 1 件。ステータスコード、レスポンス形式、修正のヒント(テーブル権限/サイト設定/内部エラーの設定)を記録します。 |
auth-pages | 認証済みページ | Phase 5.6 | ログイン後にのみアクセス可能になったページ。ユーザーが認証済みテストを行わないことを選択した場合はスキップされます。 |
auth-api | 認証済み API | Phase 5.6 | ログイン後にのみ呼び出し可能になった API エンドポイント。認証済みテストがスキップされた場合はスキップされます。 |
console | コンソールの状態 | 集約 | すべてのフェーズで観測されたコンソールエラーの集計件数。デフォルトの重大度は中です。 |
plan-alm の[Validation]タブは、この形式を直接使用します。各カテゴリはステージごとのサブタブに折りたたみ可能なグループとして表示され、集計された runOutcome(passed / passed-with-warnings / failed)によって、[Validation]タブと[Execution checklist]サブステップの両方にある[Outcome]バッジが緑/黄/赤になります。
初期リクエスト: $ARGUMENTS
Phase 1:サイト URL の解決
目標: テスト対象となる Power Pages サイトの公開 URL を特定します。
アクション
1.1 タスクリストの作成
作業を始める前に、6 つのフェーズすべてを含む完全なタスクリストを作成してください([進行状況の追跡](#progress-tracking)表を参照)。
1.2 ユーザー入力の確認
ユーザーが $ARGUMENTS に URL を指定した場合:
https://で始まることを確認します。SITE_URLとして保存し、Phase 2 に進みます。
1.3 アクティベーション状態からの自動検出
URLが指定されていない場合は、自動検出を試みます。
-
powerpages.config.jsonを検索して、プロジェクトのルートを特定します。**/powerpages.config.json -
アクティベーション状態を確認するスクリプトを実行します。
node "${PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "<PROJECT_ROOT>" -
JSONの結果を評価します。
activatedがtrueで、websiteUrlが存在する場合:websiteUrlをSITE_URLとして使用します。ユーザーに「サイトのURLを検出しました:」と伝えます。activatedがfalseの場合:ユーザーに「サイトはまだアクティベートされていません。まず/activate-siteを実行してから、このスキルを再実行してください。」と伝えます。スキルを停止します。errorが存在する場合:手順1.4に進みます。
1.4 ユーザーに確認する
自動検出に失敗した場合、または結果が不明確な場合は、AskUserQuestion を使用します。
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| テストする、デプロイ済みのPower PagesサイトのURLは何ですか?(例:https://contoso.powerappsportals.com) | サイトURL | URLを貼り付けます(説明:「その他」を選択して、サイトのURLを貼り付けてください)、URLがわかりません(説明:/activate-site を実行してサイトURLを取得するか、Power Platform管理センターで確認してください) |
ユーザーが指定したURLを SITE_URL として保存します。
出力
SITE_URLが解決され、テストを実行できる状態
フェーズ2:ブラウザーの起動と初回読み込み
目標: ブラウザーでサイトを開き、ホームページが読み込まれることを確認して、基準となるエラーを記録します。
アクション
2.1 ブラウザーのサイズを変更する
ブラウザーを標準的なデスクトップのビューポートに設定します。
browser_resizeを使用し、幅:1280、高さ:720 に設定します。
2.2 サイトに移動する
browser_navigateを使用してSITE_URLを開きます。
2.3 ページの読み込みを待つ
- ページが完全に描画されるまで、
browser_wait_forを使用して 5 秒待ちます(SPAではクライアント側のルーティングやAPI呼び出しに時間がかかる場合があります)。
2.4 ホームページを確認する
browser_snapshotを使用して、アクセシビリティのスナップショットを取得します。- スナップショットで、ページが正常に動作していることを示す要素を確認します。
- ページに意味のあるコンテンツがある(空白ではなく、一般的なエラーページでもない)。
- 「404」、「Page not found」、「500」、「Internal Server Error」、「This site can't be reached」など、よくあるエラー表示がないか確認します。
- ページにエラーが表示された場合は、ユーザーに報告し、続行するか停止するか確認します。
2.5 コンソールエラーを記録する
browser_console_messagesを level: "error" に設定して使用し、初回読み込み時のJavaScriptエラーを確認します。- 見つかったエラーを記録します。これらは最終レポートに含めます。
2.6 初回のネットワークリクエストを記録する
browser_network_requestsを includeStatic: false に設定して使用し、初回ページ読み込み時のAPI呼び出しを取得します。- フェーズ5での分析に備えて、
/_api/またはODataへのリクエストとそのステータスコードを記録します。
出力
- ブラウザーが正しいビューポートサイズで起動している
- スナップショットでホームページの読み込みと表示を確認済み
- 初回のコンソールエラーとネットワークリクエストを記録済み
- ホームページにログイン画面が表示された場合は、フェーズ3用に記録済み
フェーズ3:認証の確認
目標: サイトで認証が必要かどうかを検出し、必要に応じてログインします。Power Pagesサイトには、2つの認証レイヤーがある場合があります。
- プライベートサイトのゲート — サイト全体がプライベートです。サイトに移動すると、サイトのコンテンツが表示される前に、IDプロバイダー(Azure AD B2Cなど)へリダイレクトされます。通常、ブラウザーのURLは別のドメイン(例:
login.microsoftonline.com、*.b2clogin.com)に変わります。 - サイトレベルの認証 — サイトには誰でもアクセスできます(ホームページが読み込まれます)が、特定のページや機能の利用には、特定のWebロールを持つログイン済みユーザーが必要です。ナビゲーションに「サインイン」/「ログイン」リンクがある場合や、アクセス制限のメッセージが表示されるページがある場合は、この認証が必要です。
アクション
3.1 非公開サイトのゲートについてホームページのスナップショットを分析する
Phase 2.4 のブラウザスナップショットと現在のブラウザ URL を確認し、非公開サイトへのリダイレクトの兆候を探します。
- ページのコンテンツに、ID プロバイダーのログインフォーム(Azure AD B2C、Azure AD など)が表示されている
- ブラウザ URL が
SITE_URLと異なるドメインに変わっている(例:login.microsoftonline.com、*.b2clogin.com、カスタム ID プロバイダーのドメイン) - サイトのコンテンツが読み込まれる前に 401/403 レスポンスが返された
- ページが空白であるか、サイトのナビゲーションが表示されずに「アクセスが拒否されました」/「アクセス権がありません」と表示されている
3.2 非公開サイトのゲートに対応する
🚦 ゲート(一時停止 · test-site:3.2.private-gate-login): 外部での待機 — サイトが ID プロバイダーにリダイレクトされたため、ユーザーがログインを完了するかキャンセルするまでスキルを一時停止します。
非公開サイトのゲートが検出された場合は、AskUserQuestion を使用します。
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| このサイトは非公開です。コンテンツが読み込まれる前に、ID プロバイダーのログインページにリダイレクトされました。ログインページを表示したブラウザウィンドウが開いているはずです。このサイトにアクセスできる認証情報を使って、そこでログインしてください。ログインが完了してサイトのホームページが表示されたら、以下の「ログインしました」を選択してください。 | 非公開サイトへのログイン | ログインしました(推奨)— ログインを完了し、サイトを表示できています、テストをキャンセル — テストを停止します |
「ログインしました」の場合:
-
browser_snapshotを使用して、ユーザーが実際のサイトに移動したことを確認します(サイトのコンテンツとナビゲーションが表示され、URL がSITE_URLドメインに戻っていること)。 -
まだ ID プロバイダーのログインページが表示されている場合:
🚦 ゲート(一時停止 · test-site:3.2.login-retry): ログインがまだ完了していません — 再度確認するか、キャンセルします。
AskUserQuestionをもう一度使用します:「ログインがまだ完了していないようです。ブラウザは引き続き開いているはずです。ログインを完了して、もう一度お試しください。」- ログインが確認されるか、ユーザーがキャンセルするまで繰り返します。
-
ログインが確認されたら、Phase 2.5 と 2.6 を再実行します(読み込み済みのホームページでコンソールエラーとネットワークリクエストを取得します)。
-
ステップ 3.3 に進み、サイトレベルの認証を確認します。
「テストをキャンセル」の場合:
- スキルを停止し、アクセスの問題を解決した後に再実行できることをユーザーに伝えます。
3.3 サイトレベルの認証を分析する
ホームページが読み込まれた後(公開サイトでは直接、非公開サイトではゲートを通過した後)、スナップショットを確認し、サイトレベルの認証の兆候を探します。
- サイトのナビゲーションに「サインイン」/「ログイン」/「登録」のリンクやボタンがある
- 「このページを表示するにはサインインが必要です」などのメッセージが表示されるページがある
- 一部のエリアが認証済みユーザーに制限されていることを示すコンテンツがある
3.4 公開サイト(認証不要)に対応する
非公開サイトのゲートもサイトレベルの認証を示すものも見つからない場合:
- ユーザーに「サイトは一般公開されています。ページと API のテストを続行します。」と伝えます。
- Phase 4 に進みます。
3.5 サイトレベルの認証を処理する
🚦 ゲート(計画 · test-site:3.5.public-vs-auth): サイトにサインイン UI があります — 認証済みユーザーとしてテストするか、認証が必要なページをスキップするか、キャンセルしてください。
サイトレベルの認証を示す要素(ナビゲーション内のログインリンクなど)が検出された場合は、AskUserQuestion を使用します。
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| サイトに サインイン オプションがあります。これは、一部のページや API 呼び出しで認証が必要になる可能性があることを意味します。ブラウザーウィンドウが開いているはずです。「サインイン」をクリックし、適切な Web ロールを持つユーザーアカウントでログインできます。ログインに成功したら、以下の「ログインしました」を選択してください。 | サイト認証 | ログインしました(推奨) — サイトのログインフローからサインインしました、認証が必要なページをスキップ — 公開アクセス可能なページと API のみをテストします、テストをキャンセル — テストを停止します |
「ログインしました」の場合:
-
browser_snapshotを使用して、ユーザーがログイン済みであることを確認します(ログインリンクがユーザー名/プロフィールに置き換わっている、または認証済みコンテンツが表示されている)。 -
ログインフォームがまだ表示されている場合:
🚦 ゲート(一時停止 · test-site:3.5.login-retry): サイトレベルのログインがまだ完了していません — 再度確認するか、キャンセルしてください。
- 再度
AskUserQuestionを使用します:「ログインがまだ完了していないようです。ブラウザーはまだ開いているはずです。ログインを完了してから、もう一度お試しください。」 - ログインが確認されるか、ユーザーがキャンセルするまで繰り返します。
- 再度
-
TaskCreateを使用して、認証済みシナリオをテストする追加タスクを作成します。タスクの件名 activeForm 説明 認証済みページと API をテストする 認証済みシナリオをテスト中 ログイン済みユーザーとしてサイトを再クロールし、認証が必要なページが読み込まれること、および認証済み API 呼び出しが成功することを確認します
「認証が必要なページをスキップ」の場合:
- 公開ページのみがテストされることを記録します。一部の API 呼び出しが 401/403 を返す可能性があります。これらは報告しますが、失敗としては扱いません。
- 認証済みテストのタスクは作成しないでください。
- フェーズ 4 に進みます。
「テストをキャンセル」の場合:
- スキルを停止し、認証を解決した後に再実行できることをユーザーに伝えます。
出力
- 2 つの認証レイヤーのステータスが確定していること:
- プライベートサイトのゲート:通過、不要、またはキャンセル
- サイトレベルの認証:ログイン済み、スキップ、または不要
- 認証済みの場合:フェーズ 5.6 で認証済みテスト用の追加タスクを作成
フェーズ 4:ページのクロールとテスト
目標: サイト上で移動可能なリンクをすべて見つけ、各ページが正しく読み込まれることを確認します。
アクション
4.1 現在のページからリンクを見つける
browser_evaluate を使用して、すべての内部リンクを抽出します。
() => {
const links = Array.from(document.querySelectorAll('a[href]'));
return links
.map(a => a.href)
.filter(href => href.startsWith(window.location.origin))
.filter(href => !href.includes('#') || href.split('#')[0] !== window.location.href.split('#')[0])
.map(href => href.split('#')[0])
.filter((href, i, arr) => arr.indexOf(href) === i);
}
見つかったリンクをユーザーに提示します。
「ホームページで内部リンクが X 件見つかりました。各ページをテストします…」
4.2 各ページをテストする
見つかった URL ごとに、順番に次を行います。
- 移動:
browser_navigateを使用して URL に移動します。 - 待機:
browser_wait_forを time: 3 秒で使用し、ページが描画されるのを待ちます。 - スナップショット:
browser_snapshotを使用して、ページのコンテンツが描画されたことを確認します。 - エラーを確認:スナップショット内のエラーを示す要素(404、500、空白ページ、エラーメッセージ)を探します。
- コンソールエラー:
browser_console_messagesを level: "error" で使用して、JavaScript エラーを確認します。 - 新しいリンクを見つける:
browser_evaluate(4.1 と同じスクリプト)を使用して、キューにまだない新しい内部リンクを探します。 - 結果を記録:URL、ステータス(成功/失敗)、エラー数、メモ。
4.3 新たに見つかったリンクをクロールする
- 手順 4.2.6 で新たに見つかったリンクをテストキューに追加します。
- すべてのリンクにアクセスするか、25 ページの上限に達するまでテストを続けます。
- 上限に達した場合は、ユーザーに次のように伝えます:「25 ページのテスト上限に達しました。Y 件の追加リンクが見つかりましたが、テストされていません。」
4.4 ページテストの結果を記録する
次の項目を記録する結果リストを作成します。
- テストした URL
- 読み込みステータス(成功/失敗)
- コンソールエラーの数
- メモ(エラーメッセージ、空白ページ、リダイレクトなど)
出力
- 検出可能なすべてのページをクロール済み(最大25ページ)
- 各ページの合否ステータスを記録済み
- クロール中に見つかった新しいリンクを結果に追加済み
フェーズ 5:API リクエストをテストする
目標: サイトが行うすべての API リクエストを取得して分析し、正常に動作していることを確認します。
アクション
5.1 データ駆動型ページに再度アクセスする
データテーブル、リスト、フォーム、ダッシュボードなど、動的なコンテンツを含む API 呼び出しを行う可能性の高いページに再度移動します。フェーズ2.6またはフェーズ4で /_api/ リクエストが確認されたページを優先します。
データ駆動型ページごとに、次の操作を行います。
browser_navigateを使用してページに移動します。- API 呼び出しが完了するまで、time: 5 秒を指定して
browser_wait_forを使用します。
5.2 ネットワークリクエストを取得する
- includeStatic: false を指定して
browser_network_requestsを使用し、すべてのネットワークリクエストを取得します。 - 次のパターンに一致する API リクエストを抽出します。
/_api/— Power Pages Web API/OData エンドポイント/api/— カスタム API エンドポイントodataまたは$filter、$select、$expandクエリパラメーターを含む URL
5.3 API レスポンスを分析する
取得した API リクエストごとに、次の表に基づいて評価します。
| ステータスコード | カテゴリ | アクション |
|---|---|---|
| 200, 201, 204 | 合格 | 有効な成功レスポンス |
| 304 | 警告 | キャッシュされたレスポンス — 許容されますが、記録してください |
| 401 | 不合格 | 未認証 — 認証トークンがないか、有効期限切れです |
| 403 | 不合格 | アクセス禁止 — テーブル権限またはサイト設定に問題があります |
| 404 | 不合格 | 見つかりません — エンティティセット名またはエンドポイントが正しくありません |
| 500 | 不合格 | サーバーエラー — Dataverse 内部エラーまたはプラグインエラーです |
| その他の4xx/5xx | 不合格 | 想定外のエラー |
5.3b サーバーロジックのレスポンス構造を調べる
サーバーロジックのエンドポイント(/_api/serverlogics/<name>)は通常、標準的なエンベロープを返し、その data フィールドは成功時には一般に文字列ですが、すべてのレスポンスでそうなるとは限りません。失敗時やエッジケースでは、data が null、欠落、JSON ではない文字列、または文字列以外の値になることがあります。サーバーロジックがシリアライズされた JSON を返す場合(一般的なケース)、実際のペイロードを明らかにするには data を解析する必要があり、そのペイロード自体がさらにネストされている場合もあります。フロントエンドのコードは誤った階層まで解析したり、誤ったキーを読み取ったり、data を最終的なオブジェクトとして扱ったりすることが多く、その結果、UI の不具合が表面化しないことがあります。テストサイトでは、data が常に直接解析可能だと仮定するのではなく、フロントエンドを修正できるよう、実際に観測された正確な構造を明らかにする必要があります。
テスト対象のページで確認された /_api/serverlogics/ リクエストごとに、次の操作を行います。
-
URL からエンドポイント名(
/_api/serverlogics/<endpointName>)、HTTP メソッド、クエリ文字列パラメーター、HTTP ステータスコードを記録します。 -
リクエストが GET の場合、
browser_evaluateを使用して、同じ認証済みサイトから再実行し、レスポンス構造を取得します。 Power Pages API リクエストにはサイトの検証トークンが必要です。GET は読み取り専用ですが、認証または CSRF の例外ではありません。 ブラウザーから利用可能な場合は、サイトに既存の認証済みリクエストラッパーを優先して使用します。 それ以外の場合は、同一オリジンの/_layout/tokenhtmlエンドポイントからトークンを取得し、メモリ内に保持します。ログやレポートには決して含めないでください。 トークンエンドポイントが利用できない場合、または確認した URL が同一オリジンでない場合は、Cookie を取り込んだり未認証リクエストを送信したりせず、再実行はブロックされたと報告します。 文字列型のペイロードフィールドを段階的に解析し、各階層の構造を記録します。 確認した URL をJSON.stringify(observedUrl)でシリアライズし、その後<serialized-observed-url>を、引用符を含む完全なシリアライズ値に置き換えます。 プレースホルダーを別の文字列リテラルで囲まないでください。 次の形式のスクリプトを使用します。async () => { const url = new URL(<serialized-observed-url>, window.location.origin); if (url.origin !== window.location.origin) throw new Error('API replay must remain on the authenticated site.'); const tokenResponse = await fetch(new URL('/_layout/tokenhtml', window.location.origin).href, { credentials: 'include', redirect: 'error', headers: { Accept: 'text/html' }, }); if (tokenResponse.status !== 200) throw new Error('The site verification-token endpoint is unavailable; read-only replay is blocked.'); const tokenHtml = await tokenResponse.text(); const valueMarker = 'value="'; const valueStart = tokenHtml.indexOf(valueMarker); const valueEnd = valueStart === -1 ? -1 : tokenHtml.indexOf('"', valueStart + valueMarker.length); const token = valueStart === -1 || valueEnd === -1 ? '' : tokenHtml.slice(valueStart + valueMarker.length, valueEnd); if (typeof token !== 'string' || !token.trim()) throw new Error('The site verification token is unavailable.'); const res = await fetch(url.href, { credentials: 'include', redirect: 'error', headers: { Accept: 'application/json', '__RequestVerificationToken': token }, }); const status = res.status; const text = await res.text(); const levels = []; let current; try { current = JSON.parse(text); } catch { return { status, rawSample: text.slice(0, 2000) }; } levels.push({ type: Array.isArray(current) ? 'array' : typeof current, keys: current && typeof current === 'object' && !Array.isArray(current) ? Object.keys(current) : null, firstItemKeys: Array.isArray(current) && current[0] && typeof current[0] === 'object' ? Object.keys(current[0]) : null }); // Progressively parse common nested-string fields (data, Body, body, payload, result) const nestedKeys = ['data', 'Body', 'body', 'payload', 'result']; while (current && typeof current === 'object') { const next = nestedKeys.find(k => typeof current[k] === 'string'); if (!next) break; let parsed; try { parsed = JSON.parse(current[next]); } catch { break; } levels.push({ parsedFrom: next, type: Array.isArray(parsed) ? 'array' : typeof parsed, keys: parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? Object.keys(parsed) : null, firstItemKeys: Array.isArray(parsed) && parsed[0] && typeof parsed[0] === 'object' ? Object.keys(parsed[0]) : null }); current = parsed; } return { status, levels, rawSample: text.slice(0, 2000) }; } -
サーバーロジックへのリクエストが GET 以外の場合、再実行しないでください(データを変更する可能性があります)。すでに取得済みの
browser_network_requestsエントリーを参照し、利用可能なレスポンスメタデータを報告してください。レポートには、本文を再取得していないことを記載してください。 -
Phase 6 レポートに「サーバーロジックのレスポンス形式」セクションを作成してください。各エンドポイントについて、エンドポイント名、HTTP メソッド、ステータス、解析レベルの連鎖(各ステップで解析したキー、そのレベルでの結果の型とキー、配列の場合は最初の項目のキー)、および生データのサンプル(上記スクリプトのスライスと同様に先頭約 2000 文字)を記録してください。
envelope.success === falseかつenvelope.errorが存在する場合は、エラーをそのまま報告してください。 -
観測された形式と、フロントエンドが実際にどのようにデータを利用しているか(リポジトリ内で見つかったサービスファイル、フック、コンポーネント)を比較してください。不一致がある場合(例:UI は
envelope.data.valueをオブジェクトとして読み取る一方、実際のペイロードは解析が必要な文字列である場合や、期待されるフィールド名が観測されたキーに存在しない場合)は、その不一致を特定し、実際の形式に合わせるために必要な最小限のフロントエンド変更を推奨してください。スキルのこの段階ではフロントエンドコードを変更したり、コミットを作成したりせず、実装は別の編集/修正スキルまたはフェーズに委ねてください。 Phase 6 のレポートに、調査結果と推奨するフロントエンドの修正を記録します。
5.4 失敗時に実行可能な対処方法を提示する
API リクエストが失敗するたびに、具体的な対処方法を提示します。
- 401 Unauthorized:「このエンドポイントには認証が必要です。Phase 3 でログインをスキップした場合は、認証ありで再実行してください。それ以外の場合は、認証トークンが正しく渡されていることを確認してください。」
- 403 Forbidden on
/_api/calls:「以下を確認してください。\n 1. テーブルのアクセス許可 — このテーブルに対して、適切な Web ロールに正しいスコープと権限(読み取り、書き込みなど)が割り当てられたテーブルのアクセス許可が存在することを確認してください。\n 2. サイト設定 —Webapi/<tablename>/enabledがtrueに設定されていることを確認してください。fields の値には、通常の列には LogicalNames、参照列の読み取りには_<LogicalName>_value、@odata.bindを介したリレーションシップの書き込みには正確な Navigation Properties を使用します。OData 集計では、グループ化キー、集計対象の入力列、フィルター列、並べ替え列をすべて含めてください。\n 3. Web ロールの割り当て — 認証済みユーザーに正しい Web ロールが割り当てられていることを確認してください。」 - 404 Not Found:「エンティティセット名を確認してください(テーブルの論理名の複数形を使用します)。Dataverse にテーブルが存在し、公開されていることを確認してください。」
- 500 Internal Server Error:「詳細なエラーメッセージを取得するには、
Webapi/error/innererrorサイト設定を有効に(trueに設定)してください。再デプロイして再テストし、内部エラーの詳細を確認してください。」
5.5 フォーム送信をテストする(任意)
🚦 ゲート(同意 · test-site:5.5.form-submit): ライブサイト上でフォームを送信しようとしています。Dataverse レコードが作成または変更される可能性があります(ライブ Dataverse 環境の共有状態に対する破壊的操作です)。明示的なオプトインが必要です。
トリガー: Phase 5 で
browser_snapshotによりフォームが検出された場合。 確認する理由: テストデータを本番レコードに自動送信すると、実際の顧客データが汚染されます。 キャンセル時: 何もありません。以前の 5.x フェーズから読み取り専用 API の確認を続行します。
browser_snapshot でフォーム要素が表示され、いずれかのページでフォームが検出された場合は、操作する前に AskUserQuestion を使ってユーザーに確認します。
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| 送信時に API 呼び出しが発生する可能性があるフォームがサイト上で見つかりました。POST/PATCH API エンドポイントをテストするために、これらのフォームを操作してもよいですか?注意:Dataverse 環境のデータが作成または変更される可能性があります。 | フォームのテスト | はい、フォーム送信をテストする — テストデータが作成される可能性があることを理解しました、フォームのテストをスキップする(推奨) — 読み取り専用 API 呼び出しのみをテストする |
「はい」の場合:
browser_clickを使用して、フォームの送信ボタンを操作します。browser_wait_forを使用して、フォームの応答を待ちます。browser_network_requestsを使用して、結果として発生した POST/PATCH リクエストを取得します。- 5.3 と同じ基準で応答を分析します。
「スキップ」の場合:Phase 5.6 に進みます(認証済みテストのタスクが作成されていない場合は Phase 6 に進みます)。
出力
- 検出してテストしたすべての API エンドポイント
- 記録した HTTP ステータスコードを含む合否ステータス
- 各失敗に対して提示した実行可能な対処方法
5.6 認証済みシナリオをテストする(ユーザーがログインした場合のみ)
ユーザーが Phase 3.5 で「認証済みページをスキップ」を選択した場合、または Phase 3.3 でサイトレベルの認証が検出されなかった場合は、この手順全体をスキップします。
目標: 認証済みユーザーとしてサイトを再クロールし、ログイン後にのみ利用可能なページと API 呼び出しを検出してテストします。
「認証済みページと API をテストする」タスクを in_progress に設定します。
5.6.1 認証済みページを見つける
ログイン後、サイトのナビゲーションに、匿名ユーザーには非表示またはアクセス制限されていたリンク(例:プロフィールページ、ダッシュボード、管理パネル、アカウント管理)が追加で表示される場合があります。
SITE_URL(ホームページ)に戻ります。browser_snapshotを使って、認証済みのナビゲーションをキャプチャします。browser_evaluate(Phase 4.1 と同じリンク抽出スクリプト)を使って、内部リンクを見つけます。- Phase 4 ですでにテストしたリンクと比較します。認証前には表示されなかった新しいリンクを特定します。
新しいリンクが見つかった場合は、ユーザーに次のように伝えます。
「ログイン後に表示される、匿名ではアクセスできなかった追加ページが X 件見つかりました。それぞれをテストしています……」
5.6.2 認証済みページをテストする
新たに見つかったリンクごとに、Phase 4.2 と同じテスト手順に従います。
- ページに移動し、待機してスナップショットを取得し、エラーを確認して、コンソールエラーをキャプチャします。
- 結果を認証済みページテストとして別途記録します。
- 同じ25ページの上限を守ります(Phase 4 ですでにテストしたページも数に含めます)。
5.6.3 認証済み API 呼び出しをテストする
/_api/ リクエストを行う認証済みページごとに、次の手順を実行します。
includeStatic: falseを指定してbrowser_network_requestsを使い、API 呼び出しをキャプチャします。- Phase 5 でキャプチャした API 呼び出しと比較し、新しいエンドポイント、または以前は 401/403 を返していたものの現在は成功するエンドポイントを特定します。
- Phase 5.3 と同じ基準でレスポンスを分析します。
5.6.4 結果を記録する
Phase 6 で別のセクションに報告できるよう、認証済みテストの結果を別途記録します。
- 見つけてテストした認証済みページ(件数、合格/不合格)
- 認証済み API 呼び出し(件数、合格/不合格、ログイン後に不合格から合格に変わったエンドポイント)
「認証済みページと API をテストする」タスクを completed にします。
出力
- クロールしてテストした認証済みページ
- キャプチャして分析した認証済み API エンドポイント
- テストレポート用に別途記録した結果
Phase 6: テストレポートを作成する
**目標:**すべてのテスト結果を包括的に要約し、次のステップを提案します。
アクション
6.1 スキルの使用状況を記録する
参照:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
参照先にあるスキル追跡の手順に従い、このスキルの使用状況を記録します。--skillName "TestSite" を使用します。
6.2 ページテストの結果を提示する
結果をわかりやすい表で提示します。
## Page Test Results
| # | URL | Status | Console Errors | Notes |
|---|-----|--------|----------------|-------|
| 1 | / | Pass | 0 | Homepage loaded successfully |
| 2 | /about | Pass | 0 | |
| 3 | /products | Pass | 2 | Minor JS warnings |
| 4 | /admin | Fail | 1 | 403 Forbidden |
Pages tested: 4/4 | Passed: 3 | Failed: 1
6.3 API テストの結果を提示する
## API Test Results
| # | Endpoint | Method | Status | Notes |
|---|----------|--------|--------|-------|
| 1 | /_api/cr4fc_products | GET | 200 OK | 12 records returned |
| 2 | /_api/cr4fc_categories | GET | 200 OK | 3 records returned |
| 3 | /_api/cr4fc_orders | GET | 403 Forbidden | Missing table permissions |
API endpoints tested: 3 | Passed: 2 | Failed: 1
API リクエストがキャプチャされなかった場合は、次のように記載します。「テスト中に API リクエスト(/_api/ または OData)は検出されませんでした。このサイトでは Web API を使用していないか、API 呼び出しを発生させるために特定のユーザー操作が必要な可能性があります。」
6.3b サーバーロジックのレスポンス形式を提示する
Phase 5.3b で /_api/serverlogics/ リクエストがキャプチャされた場合は、フロントエンドの連携を実際のレスポンスに合わせて実装できるよう、専用の小見出しを追加します。解析レベルの全段階を示し、エンドポイントごとに1つのブロックを記載します。
## Server Logic Response Shapes
### /_api/serverlogics/<endpoint-name> (GET, 200)
- Level 0 (raw body): object, keys: [requestId, success, serverLogicName, data, error]
- Level 1 (parsed from `data`): object, keys: [status, items, count]
- Level 1 `items` property: array; first item keys: [id, name, ...]
- Raw sample: `{"requestId":"...","success":true,"data":"{\"status\":\"success\",\"items\":[{\"id\":\"...\",\"name\":\"...\"}],\"count\":1}", ...}`
Frontend parsing required:
const level1 = JSON.parse(envelope.data);
const items = level1.items;
フロントエンドとの不一致が検出された場合は、変更が必要なファイルと行、および必要な解析方法やフィールドアクセスの修正内容を列挙します。このスキルでは変更を適用したり、コミットしたりしないでください。このセクションは、開発者から「サーバーロジックがどのような形式を返すのかわかりません。フロントエンド側では何をすればよいですか?」と尋ねられた場合の主な成果物です。
6.4 認証済みテストの結果を提示する(該当する場合)
Phase 5.6 を実行した場合は、結果を別々の表で提示します。
## Authenticated Page Test Results
| # | URL | Status | Console Errors | Notes |
|---|-----|--------|----------------|-------|
| 1 | /profile | Pass | 0 | User profile loaded |
| 2 | /dashboard | Pass | 1 | Minor JS warning |
| 3 | /admin/settings | Fail | 0 | 403 Forbidden — insufficient web role |
Authenticated pages tested: 3 | Passed: 2 | Failed: 1
## Authenticated API Test Results
| # | Endpoint | Method | Status | Notes |
|---|----------|--------|--------|-------|
| 1 | /_api/cr4fc_orders | GET | 200 OK | Previously 403 — now accessible after login |
| 2 | /_api/cr4fc_userprofiles | GET | 200 OK | Only visible after auth |
Authenticated API endpoints tested: 2 | Passed: 2 | Failed: 0
ログイン後に追加のページや API が見つからなかった場合は、次のように記載します。「認証後に追加のページや API エンドポイントは見つかりませんでした。認証済みユーザーには匿名の訪問者と同じコンテンツが表示されます。」
6.5 全体の概要を提示する
## Overall Test Summary
| Category | Tested | Passed | Failed | Warnings |
|--------------------------|--------|--------|--------|----------|
| Pages (public) | 4 | 3 | 1 | 0 |
| Pages (authenticated) | 3 | 2 | 1 | 0 |
| API Endpoints (public) | 3 | 2 | 1 | 0 |
| API Endpoints (auth) | 2 | 2 | 0 | 0 |
| Console Errors | — | — | — | 2 |
Overall: X/Y checks passed
認証済みテストを省略した場合は、表から認証済みの行を省きます。
6.6 推奨事項を提示する
各失敗について、フェーズ 5.4 の具体的な修正ガイダンスを改めて提示します。推奨事項はカテゴリごとにまとめます。
- テーブルのアクセス許可に関する問題 →
/create-webrolesを実行するか、テーブルのアクセス許可を手動で設定します - サイト設定に関する問題 →
Webapi/<table>/enabledとWebapi/<table>/fieldsの設定を確認します - 認証に関する問題 →
/setup-authを実行します - エンドポイントの欠落 →
/setup-datamodelを使用して、テーブルが Dataverse に存在することを確認します - サーバーエラー → 診断用に
Webapi/error/innererrorサイト設定を有効にします
6.7 ブラウザーを閉じる
browser_closeを使用して、ブラウザーセッションをクリーンアップします。
6.7a 機械可読レポートを作成する(docs/alm/last-test-site.json)
他のスキル(特に plan-alm)が Markdown の概要を再解析せずに実行結果を取り込めるよう、必ず構造化 JSON レポートを作成します。このファイルは実行ごとに上書きされます。書き込み前に docs/alm/ ディレクトリが存在することを確認してください — node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"。
構造:
{
"url": "https://contoso.powerappsportals.com",
"stageName": "Staging",
"runAt": "2026-04-29T08:50:00.000Z",
"durationSec": 95,
"runOutcome": "passed | passed-with-warnings | failed",
"summary": {
"critical": 0,
"high": 1,
"medium": 0,
"low": 2,
"total": 3,
"automated": 2,
"manual": 1,
"passed": 2,
"failed": 1,
"skipped": 0
},
"categories": [
{
"id": "site-load",
"name": "Site Load",
"icon": "📦",
"tests": [
{
"id": "t01",
"name": "Homepage returns 200 OK",
"severity": "critical | high | medium | low",
"type": "automated | manual",
"status": "passed | failed | skipped",
"description": "Short why-this-matters sentence.",
"steps": ["GET /", "Expect 200"],
"expected": "200 OK",
"actual": "200 OK",
"validates": "Site activation"
}
]
}
]
}
カテゴリのマッピング — 所見があったテストサイトのフェーズごとに 1 つのカテゴリを出力します。利用側が認識できるよう、次の固定 id 値を使用してください。
id | name | ソースフェーズ |
|---|---|---|
site-load | サイトの読み込み | フェーズ 2(ホームページの HTTP、リダイレクト、レンダリング) |
authentication | 認証 | フェーズ 3(ログインリダイレクト、匿名アクセスのゲート) |
page-crawl | ページのクロール | フェーズ 4(テストした各ページが 1 つのカードになります) |
web-api | Web API | フェーズ 5(テストした各エンドポイントが 1 つのカードになります) |
auth-pages | 認証済みページ | フェーズ 5.6 のページテスト |
auth-api | 認証済み API | フェーズ 5.6 の API テスト |
console | コンソールの状態 | すべてのページにわたるコンソールエラーの集計 |
コンポーネント数、バージョン番号、または以前のデプロイに関するその他の実行固有データを埋め込んだ、トップレベルの notes 文字列は含めないでください。 実際に発生した失敗例:2 回前のデプロイ時の "4,037 components" が last-test-site.json の notes フィールドにハードコードされ、その後、最新のデプロイで 4,051 コンポーネントがリリースされたにもかかわらず、plan-alm に表示されました。マーカーに自由形式の説明が必要な場合は、CURRENT run のデータのみに基づいてください — 数値を実行間で引き継いではいけません。構造化された summary と categories[] が監査証跡です。文章による概要は JSON マーカーではなく、レンダリングされた HTML に記載してください。
重大度のルール(テストカードごとに適用):
- HTTP 5xx レスポンス →
critical - 公開ページまたは文書化された公開 API での HTTP 4xx レスポンス →
high - 認証専用リソースへの匿名アクセスでの HTTP 4xx レスポンス →
low(想定されるゲート) - それ以外は合格しているページでのコンソールエラー →
medium - その他すべての所見(情報のみ) →
low
summary オブジェクトの算出方法 — 注意して読んでください。 summary の各区分は、カテゴリ自体から導き出したり low にデフォルト設定したりせず、すべての categories[].tests[] にある各テストの severity フィールドを数えて集計する必要があります。plan-alm の Validation タブと、レンダリングされた HTML の重大度グリッドはどちらも summary を直接参照します — 実際の重大度にかかわらず、すべてのテストが low になると、重大な失敗が存在していてもすべてのステージが正常に見えてしまいます。実際の再現例(Citizens ポータル、2026-05-21):重大度が critical / high / high / low の 4 件のテストが、エージェントがテストごとの集計を省略したため、summary: {critical:0, high:0, medium:0, low:4} にまとめられました。
次のように算出します。
const summary = { critical: 0, high: 0, medium: 0, low: 0, total: 0, automated: 0, manual: 0, passed: 0, failed: 0, skipped: 0 };
for (const cat of (report.categories || [])) {
for (const test of (cat.tests || [])) {
summary.total += 1;
const sev = String(test.severity || '').toLowerCase();
if (sev === 'critical') summary.critical += 1;
else if (sev === 'high') summary.high += 1;
else if (sev === 'medium') summary.medium += 1;
else summary.low += 1; // default for unknown / missing severity
if (test.type === 'manual') summary.manual += 1; else summary.automated += 1;
if (test.status === 'failed') summary.failed += 1;
else if (test.status === 'skipped') summary.skipped += 1;
else summary.passed += 1;
}
}
plan-alm レンダラー(render-alm-plan.js の buildValidationStagePane)は、「Critical」「High」「Medium」「Low」の 4 つの重大度カードを個別に表示します。summary の各区分には、「Medium / Low」の合計値ではなく、その重大度に該当する実際の件数を反映する必要があります。
ステータスのルール:
passed— アサーションが成立しました(200、コンソールエラーなし、期待されるリダイレクトなど)failed— アサーションが成立しませんでした(5xx、200が期待される場面での4xx、ログインフローの失敗など)skipped— フェーズまたはテストが意図的にスキップされました(例:3.5 でユーザーが「Skip authenticated pages」を選択)
summary は categories から算出されます:
critical/high/medium/low— 各重大度のテスト数。ステータスにかかわらず集計されます(すべて成功した場合でも、レビュー担当者がテスト対象全体を確認できるようにします)。total— テストカードの合計数。automated/manual—type別の内訳。passed/failed/skipped—status別の内訳。
runOutcome は集計された判定結果です:
- いずれかのテストの
statusが"failed"で、かつseverityが"critical"または"high"の場合はfailed。 - critical/high の失敗がなくても、
summary.failed > 0またはコンソールエラーが記録されている場合はpassed-with-warnings。 - それ以外の場合は
passed。
ファイルを書き込みます(Node.js。プロジェクトのルートから実行):
node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"
node -e "require('fs').writeFileSync('docs/alm/last-test-site.json', process.argv[1])" "$(cat <<'EOF'
{...the JSON above...}
EOF
)"
または、plan-alm から呼び出された場合、オーケストレーターが JSON をインラインで渡すこともできます。いずれの場合も、マーカーファイルの場所は固定です:docs/alm/last-test-site.json(docs/alm/last-deploy.json および docs/alm/last-pipeline.json と同じ階層)。
判明している場合は、必ずマーカーに stageName を含めてください。 エージェントは上流のコンテキストからステージ名を取得します。たとえば、docs/alm/last-deploy.json の stageName、プランの stages[]、またはユーザーからの明示的な指定です。ステージを特定できない場合(例:任意の URL に対して test-site を単独で実行する場合)は、stageName を null に設定してください。リフレッシュヘルパーには代替の解決方法がありますが、このフィールドを明示するのが最も確実です。
6.7b ALM プランを更新する(存在する場合)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase test-site \
--stageName "{stageName}" \
--render
{stageName} は、この実行でテストしたステージ名です(例:Staging、Production)。不明な場合は空文字列を渡してください。refreshTestSite は、(1)マーカーの stageName フィールド(上記 6.7a で設定)、次に(2)planData.stages に対象ステージが 1 つだけある場合はそのステージ、の順でフォールバックします。明示的な stageName もマーカーの stageName もない複数ステージのプランは記録されません(ステージごとの validationRun 更新を行わずに再描画されます)。可能な場合は必ず明示的に渡してください。
ヘルパーは docs/alm/last-test-site.json を読み込み、分類されたテスト結果を planData.validationRuns[{resolvedStage}] に設定して、docs/alm-plan.html を再描画することで Validation タブをすぐに更新します。docs/.alm-plan-data.json が存在しない場合(単独での呼び出し、プロジェクトにプランがない場合)、ヘルパーは ok:false を返して何もせずに終了します。無条件で実行しても安全です。
次のステップをユーザーに案内してください(ユーザー主導の順序付け)。 ヘルパーの stdout JSON には nextStep: { name, skill: string | null } | null が含まれます。null 以外の場合は skill に応じて分岐します。skill が null 以外の場合は、ユーザーに 「プランを更新しました。プランの次のステップ:{nextStep.name} → 準備ができたら {nextStep.skill} を実行してください。」 と伝えます。skill が null の場合(Finalize のような内部ステップで、ユーザーが実行するコマンドがない場合)は、ステップ名のみを伝えます。「プランを更新しました。プランの次のステップ:{nextStep.name}。」 とし、run null は絶対に表示しないでください。(通常は、次のステージのデプロイ/有効化/テストです。)null の場合(これが最後のステップの場合)、またはヘルパーが ok:false を返した場合は、次のステップについて何も伝えません。次のスキルを自動実行してはいけません。実行はユーザーが主導します。
6.8 次のステップを提案する
テスト結果に基づいて、関連するスキルを提案します:
- API の失敗が見つかった場合:
/integrate-webapi— Web API のサイト設定とテーブルのアクセス許可を修正します - 認証に問題がある場合:
/setup-auth— 認証プロバイダーを設定します - ページにエラーがあった場合:サイトのコードを確認し、
/deploy-siteで再デプロイします - すべてのテストに合格した場合:サイトは正常に動作しています!検索エンジン最適化には
/add-seoの利用をご検討ください
出力
- ページと API の合格/不合格を含む包括的なテストレポート
- 各失敗に対する実行可能な推奨事項
- ブラウザーセッションを終了
- 次のステップを提案
重要な注意事項
すべてのフェーズを通じて
- TaskCreate/TaskUpdate を使用して、すべてのフェーズの進捗を追跡します
- このスキルは読み取り専用です — ファイルやデータは変更しません
- ユーザーに代わってログインしないでください — 必ずブラウザーウィンドウからログインするようユーザーに依頼します
- エラーを明確に提示します — ページまたは API でエラーが発生した場合は、具体的な URL とエラーの詳細を含めます
主な判断ポイント
- フェーズ 1.3:サイトが有効化されていない場合は、処理を停止して
/activate-siteに移動します - フェーズ 1.4:URL を自動検出できない場合は、必ずユーザーに確認します
- フェーズ 3.2:サイトが非公開(ID プロバイダーにリダイレクトされる)の場合は、ユーザーにログインを依頼します — 回避できません
- フェーズ 3.5:サイトレベルの認証が利用できる場合は、ログインするかスキップするかをユーザーに確認します — 自動ログインはできません
- フェーズ 4.3:無限ループを防ぐため、25 ページでクロールを停止します
- フェーズ 5.5:データを作成または変更する可能性があるフォームを操作する前に、ユーザーから明示的な許可を得ます
進捗の追跡
フェーズ 1 を開始する前に、TaskCreate を使用してすべてのフェーズを含むタスクリストを作成します。
| タスクの件名 | activeForm | 説明 |
|---|---|---|
| サイト URL の解決 | サイト URL を解決中 | ユーザー入力、有効化ステータスの確認、またはコンテキストから URL を取得します |
| ブラウザーを起動して初回の読み込みを確認 | ブラウザーでサイトを読み込み中 | サイトに移動し、ホームページが読み込まれることを確認して、初期エラーを記録します |
| 認証要件の確認 | 認証を確認中 | サイトに認証が必要か検出し、必要に応じてログインに対応します |
| すべてのページをクロールしてテスト | サイトのページをクロール中 | リンクを見つけ、各ページに移動して、読み込みを確認し、コンソールエラーを確認します |
| API リクエストのテスト | API エンドポイントをテスト中 | ネットワークリクエストを記録し、API レスポンスを確認して、エラーを分析します |
| テストレポートの作成 | テストレポートを作成中 | テストしたすべてのページと API の概要を提示し、次のステップを提案します |
各タスクの開始時に TaskUpdate を使用して in_progress に設定し、完了時に completed に設定します。
フェーズ 1:サイト URL の解決から開始します