# TestWorkspaceApp ワークスペース・アプリをヘッドレスブラウザで実起動し、操作・期待値検証・ファイルの変化確認を行う E2E テストツール。`workspace-app` ピースの verify ステップが自動的に呼び出す。 ## 入力パラメータ | パラメータ | 型 | 必須 | 説明 | |-----------|-----|------|------| | `space` | string | ✓ | テスト対象スペースの ID | | `app` | string | ✓ | `apps/` 配下のアプリフォルダ名(パスではなく名前のみ) | | `entry` | string | — | エントリー HTML のパス(スペースファイルルートからの相対)。省略時は `apps/{app}/index.html` | | `seed_files` | array | — | テスト前にワークスペースへ書き込むファイル群(下記参照) | | `steps` | array | — | ブラウザアクション列(`BrowseWebAction` と同じ形式、下記参照) | | `expect` | array | — | 検証する期待値リスト(下記参照) | | `timeout_ms` | number | — | ブラウザ操作全体のタイムアウト(ミリ秒、デフォルト 30000) | ### seed_files テスト開始前にワークスペースへ書き込むファイルを指定する。アプリが読み取る入力データを事前配置するために使う。 ```json "seed_files": [ { "path": "output/notes.md", "content": "# Hello\nworld" } ] ``` - `path` はワークスペースルートからの相対パス(`resolveAndGuard` でパストラバーサルを防ぐ) - `content` は UTF-8 文字列 - 書き込んだファイルは `file_changes` に記録される(bytes はシード時の書き込みサイズ) ### steps(BrowseWebAction 形式) ハーネスページ上で実行するブラウザ操作を配列で渡す。型は `BrowseWebAction` と共通。 | type | 追加フィールド | 説明 | |------|--------------|------| | `goto` | `url` | 指定 URL へ移動(ハーネス URL は自動で最初に追加されるため通常不要) | | `click` | `selector` または `ref` | 要素をクリック | | `fill` | `selector` または `ref`, `value` | フォーム入力 | | `screenshot` | `value`(ファイル名) | スクリーンショットを撮影 | | `getText` | — | ページのテキストを取得(期待値チェックに使われる) | | `dumpHtml` | — | ページの HTML を取得 | | `wait` | `ms` | 指定ミリ秒待機 | ハーネスは起動時に自動的に `goto → wait(2000ms) → getText` を実行してからユーザー指定ステップを追加する。`goto` を先頭に追加する必要はない。 ### expect(期待値リスト) | kind | フィールド | 説明 | |------|-----------|------| | `text` | `target`(CSS セレクタ), `contains` | ページテキスト全体が `contains` を含む。`target` は情報的なセレクタ(精度向上には `getText` アクションを先に実行) | | `file` | `target`(ワークスペース相対パス), `contains` | 指定ファイルが存在し、その内容が `contains` を含む | **重要**: `kind` は `"text"` または `"file"` のみ有効。他の値は常に失敗として記録される(サイレントパスなし)。`contains` が空文字列の場合も評価は行われる(空文字列はどの文字列にも含まれるため通過)。 ```json "expect": [ { "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存OK" }, { "kind": "file", "target": "output/notes.md", "contains": "edited-by-e2e" } ] ``` ## 戻り値 ```json { "ok": true, "failures": [], "screenshots": ["app-test-myapp-final.png"], "console_errors": [], "bridge_calls": [], "file_changes": [ { "path": "output/notes.md", "bytes": 14 } ] } ``` | フィールド | 説明 | |-----------|------| | `ok` | `failures` が空なら `true` | | `failures` | 失敗した期待値の説明(配列) | | `screenshots` | 撮影したスクリーンショットのファイル名一覧 | | `console_errors` | ブラウザコンソールのエラーメッセージ | | `bridge_calls` | **V1 では常に `[]`**(ヘッドレス環境ではブリッジ呼び出しの観測不可) | | `file_changes` | seed_files で書き込んだファイルの一覧と byte 数 | ## テンプレートの `e2e.example.json` を使う `docs/examples/workspace-apps/{note-editor,data-viewer,form-input,dashboard}/e2e.example.json` にひな型が入っている。TestWorkspaceApp に渡す JSON の例として使えるので、新しいアプリを作る際はここをコピーして改変する。 ### note-editor の例 ```json { "app": "note-editor", "seed_files": [ { "path": "output/note.md", "content": "seeded-content" } ], "steps": [ { "type": "click", "selector": "[data-testid=\"load\"]" }, { "type": "getText", "selector": "[data-testid=\"status\"]" }, { "type": "fill", "selector": "[data-testid=\"body\"]", "value": "edited-by-e2e" }, { "type": "click", "selector": "[data-testid=\"save\"]" }, { "type": "getText", "selector": "[data-testid=\"status\"]" } ], "expect": [ { "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存OK" }, { "kind": "file", "target": "output/note.md", "contains": "edited-by-e2e" } ] } ``` ## 注意事項(ゴッチャ) ### (a) seed_files の書き込みは本物の `output/` に入る seed_files で書き込んだファイルはワークスペースの実際のパスに書かれる。テスト用の一時領域ではなく、`output/note.md` を指定すれば本物の `output/note.md` が変更される。テスト後のクリーンアップはユーザー(またはエージェント)が行う必要がある。 ### (b) 書き込み確認ダイアログはハーネス側で自動承認される ブラウザ内の `writeFile` / `deleteFile` ブリッジ呼び出しは、`/app-harness` ルートではダイアログが自動承認される。本番の `/ui/app/...` 上では確認ダイアログが表示されるので挙動が異なる。 ### (c) UI のビルドが必要 `/app-harness` ルートは Vite でビルドされた UI から配信される。`npm run build:ui` を実行していないと 404 になり、テストが失敗する。開発中は `cd ui && npm run dev` を起動してから dev モードで試すこともできる。 ### (d) bridge_calls は V1 では常に空 ヘッドレスブラウザ環境ではブリッジ呼び出し(`readFile` / `writeFile` など)の観測が技術的に困難なため、`bridge_calls` フィールドは常に `[]` を返す。ブリッジの動作確認は `expect` の `kind: "file"` でファイルの変化を確認することで代替する。 ## ワークフロー例 ``` # 1. アプリを作成 Write({ path: "apps/note-editor/index.html", content: "..." }) # 2. E2E テストで動作確認 TestWorkspaceApp({ space: ctx.spaceId, app: "note-editor", seed_files: [{ path: "output/test-note.md", content: "# Test" }], steps: [ { type: "wait", ms: 1500 }, { type: "getText" }, { type: "screenshot", value: "after-load.png" } ], expect: [ { kind: "text", target: "body", contains: "Test" } ] }) ``` テストが通れば `ok: true` が返り、failures は空になる。