154 lines
7.2 KiB
Markdown
154 lines
7.2 KiB
Markdown
# 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 は空になる。
|