# 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 は空になる。
