Skip to content

Commit a43e6f5

Browse files
felixtrzmeta-codesync[bot]
authored andcommitted
refactor(test-skills): restructure test skills as self-contained architecture
Summary: Replace the dual-purpose v1/v2 skill structure with self-contained skills that work both standalone and as orchestrated sub-agents. Individual skills (9): - Remove Phase 1-4 outer structure and BEGIN/END SUB-AGENT markers - Add Step 1-5 structure (install, start server, verify, test, cleanup) - Use MCPCALL shorthand with dynamic port discovery - Add recovery/retry guidance for transient failures - Remove Follower suite from test-ui (not present in poke example) Orchestrator (test-all): - Orchestrator starts dev servers (sub-agents can't run background processes) - Dynamic port discovery via .mcp.json files - Sub-agents read skill files directly, start from Step 3 - Wait for completion notifications instead of polling - No improvised bash commands — only pre-approved node scripts New scripts: - scripts/test-prep.mjs: clone/install/cleanup example directories - scripts/test-servers.mjs: start/ports/stop dev server lifecycle - All orchestrator bash operations converted to node scripts (zero permission prompts) Settings: add allowlist entries for pnpm, npx, npm run fresh:install Reviewed By: zjm-meta Differential Revision: D95235621 fbshipit-source-id: 345a359881f045ec60f5b612fd97a9f81b898e6b
1 parent 4612761 commit a43e6f5

File tree

14 files changed

+2241
-1329
lines changed

14 files changed

+2241
-1329
lines changed

.claude/settings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@
3535
"mcp__iwsdk-dev-mcp__ecs_diff",
3636
"Bash(npm run dev *)",
3737
"Bash(npm run fresh:dev *)",
38+
"Bash(npm run fresh:install *)",
39+
"Bash(pnpm install)",
40+
"Bash(pnpm build:tgz)",
41+
"Bash(pnpm -v)",
3842
"Bash(lsof *)",
3943
"Bash(kill *)",
4044
"Bash(sleep *)",
41-
"Bash(claude *)",
42-
"Bash(unset *)"
45+
"Bash(node *)",
46+
"Bash(npx *)"
4347
]
4448
},
4549
"enableAllProjectMcpServers": true,

.claude/skills/test-all/SKILL.md

Lines changed: 155 additions & 182 deletions
Large diffs are not rendered by default.

.claude/skills/test-audio/SKILL.md

Lines changed: 149 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,206 @@
11
---
22
name: test-audio
3-
description: Automated test for the audio system (AudioSource loading, playback state, stop). Tests ECS audio state transitions using IWER MCP tools against the audio example. Dynamic entity discovery. Run from examples/audio/ with the dev server running. Note that actual audio output cannot be verified — only ECS state.
4-
argument-hint: [--suite loading|playback|stop|all]
3+
description: "Test audio system (AudioSource loading, playback state, stop, spatial audio) against the audio example using mcp-call.mjs WebSocket CLI."
4+
argument-hint: "[--suite loading|playback|stop|all]"
55
---
66

77
# Audio System Test
88

9-
Automated test suite for verifying AudioSource loading, playback state transitions, and AudioSystem behavior using IWER MCP tools.
9+
Run 6 test suites covering audio loading, playback trigger, stop, system registration, component schema, and stability.
1010

11-
**Target Example:** `examples/audio`
11+
All tool calls go through `scripts/mcp-call.mjs` via WebSocket — no MCP server, no permission prompts.
1212

13-
Run this skill from the `examples/audio/` directory with the dev server running (`npm run dev`).
13+
**Configuration:**
14+
- EXAMPLE_DIR: /Users/felixz/Projects/immersive-web-sdk/examples/audio
15+
- ROOT: /Users/felixz/Projects/immersive-web-sdk
1416

15-
**Important**: Actual audio output cannot be verified via MCP tools. These tests verify ECS state transitions only (loading, play requested, is playing, stop).
16-
17-
## Pre-test Setup
17+
**SHORTHAND**: Throughout this document, `MCPCALL` means:
18+
```
19+
node /Users/felixz/Projects/immersive-web-sdk/scripts/mcp-call.mjs --port <PORT>
20+
```
21+
where `<PORT>` is the port number discovered in Step 2.
1822

23+
**Tool calling pattern**: Every tool call is a Bash command using the MCPCALL shorthand:
1924
```
20-
mcp__iwsdk-dev-mcp__browser_reload_page
21-
mcp__iwsdk-dev-mcp__xr_accept_session
22-
mcp__iwsdk-dev-mcp__browser_get_console_logs(count: 20, level: ["error"]) → no errors (audio autoplay warnings are acceptable)
25+
MCPCALL --tool <TOOL_NAME> --args '<JSON_ARGS>' 2>/dev/null
2326
```
2427

25-
---
28+
- `<TOOL_NAME>` uses MCP-style names (e.g. `browser_reload_page`, `xr_accept_session`). The script handles translation internally.
29+
- `<JSON_ARGS>` is a JSON object string. Omit `--args` if no arguments needed.
30+
- Output is JSON on stdout. Parse it to check assertions.
31+
- Use `--timeout 20000` for operations that may take longer (reload, accept_session, screenshot).
32+
- Always append `2>/dev/null` to suppress TLS warnings.
2633

27-
## Test Suites
34+
**IMPORTANT**: Run each Bash command one at a time. Parse the JSON output and verify assertions before moving to the next command. Do NOT chain multiple mcp-call commands together.
2835

29-
### Suite 1: Audio Loading
36+
**IMPORTANT**: When the instructions say "wait N seconds", use `sleep N` as a separate Bash command.
37+
38+
**IMPORTANT**: Boolean values in `ecs_set_component` must be actual JSON booleans (`value: true`), NOT strings (`value: "true"`). Strings silently fail to coerce.
3039

31-
**What we're testing**: AudioSource loads its buffer automatically.
40+
---
3241

33-
#### Test 1.1: Find Audio Entity
42+
## Step 1: Install Dependencies
3443

44+
```bash
45+
cd /Users/felixz/Projects/immersive-web-sdk/examples/audio && npm run fresh:install
3546
```
36-
mcp__iwsdk-dev-mcp__ecs_find_entities(withComponents: ["AudioSource"])
37-
→ At least 1 entity. Save as <audio>.
47+
48+
Wait for this to complete before proceeding.
49+
50+
---
51+
52+
## Step 2: Start Dev Server
53+
54+
Start the dev server as a background task using the Bash tool's `run_in_background: true` parameter:
55+
56+
```bash
57+
cd /Users/felixz/Projects/immersive-web-sdk/examples/audio && npm run dev
3858
```
3959

40-
The audio example uses a GLXF level that creates entities via composition. The Spinner entity has an AudioSource.
60+
**IMPORTANT**: This command MUST be run with `run_in_background: true` on the Bash tool — do NOT append `&` to the command itself.
61+
62+
Once the background task is launched, poll the output for Vite's ready message (up to 60s). Read the task output or use `tail` to watch for a line containing `Local:`. The output will contain a URL like `https://localhost:5173/`. Extract the port number from this URL and save it as `<PORT>`. All subsequent `MCPCALL` commands use this port.
4163

42-
#### Test 1.2: Verify Loaded State
64+
If the server fails to start within 60 seconds, report FAIL for all suites and skip to Step 5.
4365

66+
---
67+
68+
## Step 3: Verify Connectivity
69+
70+
```bash
71+
MCPCALL --tool ecs_list_systems 2>/dev/null
4472
```
45-
mcp__iwsdk-dev-mcp__ecs_query_entity(entityIndex: <audio>, components: ["AudioSource"])
73+
74+
This must return JSON with a list of systems. If it fails:
75+
1. Check the dev server output for errors
76+
2. Try killing and restarting the server (Step 2)
77+
3. If it still fails, report FAIL for all suites and skip to Step 5
78+
79+
---
80+
81+
## Step 4: Run Test Suites
82+
83+
### Pre-test Setup
84+
85+
Run these commands in order:
86+
87+
1. `MCPCALL --tool browser_reload_page --timeout 20000 2>/dev/null`
88+
Then: `sleep 3`
89+
90+
2. `MCPCALL --tool xr_accept_session --timeout 20000 2>/dev/null`
91+
Then: `sleep 2`
92+
93+
3. `MCPCALL --tool browser_get_console_logs --args '{"count":20,"level":["error"]}' 2>/dev/null`
94+
Assert: No error-level logs. Audio autoplay warnings are acceptable.
95+
96+
---
97+
98+
### Suite 1: Audio Loading
99+
100+
**Test 1.1: Find Audio Entity**
101+
```bash
102+
MCPCALL --tool ecs_find_entities --args '{"withComponents":["AudioSource"]}' 2>/dev/null
46103
```
104+
Assert: At least 1 entity. Save the first as `<audio>`.
105+
106+
The audio example uses a GLXF level that creates entities via composition. The Spinner entity has an AudioSource.
47107

48-
**Assert**:
108+
**Test 1.2: Verify Loaded State**
109+
```bash
110+
MCPCALL --tool ecs_query_entity --args '{"entityIndex":<audio>,"components":["AudioSource"]}' 2>/dev/null
111+
```
112+
Assert:
49113
- `src` contains an audio file path (e.g., `.mp3`)
50114
- `_loaded` = `true` (buffer loaded)
51115
- `_loading` = `false` (not currently loading)
52116
- `_isPlaying` = `false` (not playing yet — unless autoplay is set)
53117
- `volume` = `1`
54118
- `positional` = `true`
55119

56-
#### Test 1.3: Pool Created
57-
58-
**Assert**: `_pool` exists with `available` array matching `maxInstances`.
120+
**Test 1.3: Pool Created**
121+
Assert: `_pool` exists with `available` array matching `maxInstances`.
59122

60123
---
61124

62125
### Suite 2: Playback Trigger
63126

64-
**What we're testing**: Setting `_playRequested: true` triggers the AudioSystem to play.
65-
66-
#### Test 2.1: Request Play
67-
127+
**Test 2.1: Request Play**
128+
```bash
129+
MCPCALL --tool ecs_set_component --args '{"entityIndex":<audio>,"componentId":"AudioSource","field":"_playRequested","value":true}' 2>/dev/null
68130
```
69-
mcp__iwsdk-dev-mcp__ecs_set_component(entityIndex: <audio>, componentId: "AudioSource",
70-
field: "_playRequested", value: "true")
71-
```
72-
73-
**Assert**: `_playRequested` was consumed (response shows `newValue: false` — the AudioSystem processed it within the same frame).
131+
Assert: `_playRequested` was consumed (response shows `newValue: false` — the AudioSystem processed it within the same frame).
74132

75-
#### Test 2.2: Play with Loop for Observable State
133+
**Test 2.2: Play with Loop for Observable State**
76134

77-
To observe `_isPlaying: true`, set `loop: true` first, then request play:
135+
Set `loop: true` first, then request play:
136+
```bash
137+
MCPCALL --tool ecs_set_component --args '{"entityIndex":<audio>,"componentId":"AudioSource","field":"loop","value":true}' 2>/dev/null
78138
```
79-
mcp__iwsdk-dev-mcp__ecs_set_component(entityIndex: <audio>, componentId: "AudioSource",
80-
field: "loop", value: "true")
81-
mcp__iwsdk-dev-mcp__ecs_set_component(entityIndex: <audio>, componentId: "AudioSource",
82-
field: "_playRequested", value: "true")
139+
```bash
140+
MCPCALL --tool ecs_set_component --args '{"entityIndex":<audio>,"componentId":"AudioSource","field":"_playRequested","value":true}' 2>/dev/null
83141
```
84142

85143
Then query:
144+
```bash
145+
MCPCALL --tool ecs_query_entity --args '{"entityIndex":<audio>,"components":["AudioSource"]}' 2>/dev/null
86146
```
87-
mcp__iwsdk-dev-mcp__ecs_query_entity(entityIndex: <audio>, components: ["AudioSource"])
88-
→ _isPlaying: true (looping sound keeps playing)
89-
```
147+
Assert: `_isPlaying` = `true` (looping sound keeps playing).
90148

91149
---
92150

93151
### Suite 3: Stop
94152

95-
#### Test 3.1: Request Stop
96-
153+
**Test 3.1: Request Stop**
154+
```bash
155+
MCPCALL --tool ecs_set_component --args '{"entityIndex":<audio>,"componentId":"AudioSource","field":"_stopRequested","value":true}' 2>/dev/null
97156
```
98-
mcp__iwsdk-dev-mcp__ecs_set_component(entityIndex: <audio>, componentId: "AudioSource",
99-
field: "_stopRequested", value: "true")
100-
```
101-
102-
**Assert**: `_stopRequested` consumed, `_isPlaying` becomes `false`.
157+
Assert: `_stopRequested` consumed, `_isPlaying` becomes `false`.
103158

104159
---
105160

106161
### Suite 4: System Registration
107162

163+
```bash
164+
MCPCALL --tool ecs_list_systems 2>/dev/null
108165
```
109-
mcp__iwsdk-dev-mcp__ecs_list_systems
110-
→ AudioSystem at priority 0
111-
→ Config keys: enableDistanceCulling, cullingDistanceMultiplier
112-
→ audioEntities: ≥ 1
113-
```
166+
Assert:
167+
- AudioSystem at priority 0
168+
- Config keys: `enableDistanceCulling`, `cullingDistanceMultiplier`
169+
- `audioEntities` >= 1
114170

115171
---
116172

117173
### Suite 5: Component Schema
118174

175+
```bash
176+
MCPCALL --tool ecs_list_components 2>/dev/null
119177
```
120-
mcp__iwsdk-dev-mcp__ecs_list_components
121-
→ AudioSource fields:
122-
Core: src (FilePath), volume (Float32), loop (Boolean), autoplay (Boolean)
123-
Spatial: positional (Boolean), refDistance, rolloffFactor, maxDistance, distanceModel, coneInnerAngle, coneOuterAngle, coneOuterGain
124-
Behavior: playbackMode (Enum), maxInstances (Int8), crossfadeDuration (Float32), instanceStealPolicy (Enum)
125-
Control: _playRequested, _pauseRequested, _stopRequested (Boolean), _fadeIn, _fadeOut (Float32)
126-
State: _pool (Object), _instances (Object), _isPlaying (Boolean), _buffer (Object), _loaded, _loading (Boolean)
127-
```
178+
Assert AudioSource fields:
179+
- Core: `src` (FilePath), `volume` (Float32), `loop` (Boolean), `autoplay` (Boolean)
180+
- Spatial: `positional` (Boolean), `refDistance`, `rolloffFactor`, `maxDistance`, `distanceModel`, `coneInnerAngle`, `coneOuterAngle`, `coneOuterGain`
181+
- Behavior: `playbackMode` (Enum), `maxInstances` (Int8), `crossfadeDuration` (Float32), `instanceStealPolicy` (Enum)
182+
- Control: `_playRequested`, `_pauseRequested`, `_stopRequested` (Boolean), `_fadeIn`, `_fadeOut` (Float32)
183+
- State: `_pool` (Object), `_instances` (Object), `_isPlaying` (Boolean), `_buffer` (Object), `_loaded`, `_loading` (Boolean)
128184

129185
---
130186

131187
### Suite 6: Stability
132188

189+
```bash
190+
MCPCALL --tool browser_get_console_logs --args '{"count":30,"level":["error","warn"]}' 2>/dev/null
133191
```
134-
mcp__iwsdk-dev-mcp__browser_get_console_logs(count: 30, level: ["error", "warn"])
135-
→ No errors. Audio autoplay warnings are acceptable.
136-
```
192+
Assert: No application-level errors. Audio autoplay warnings and pre-existing 404 resource errors from page load are acceptable.
137193

138194
---
139195

140-
## Results Summary
196+
## Step 5: Cleanup & Results
197+
198+
Kill the dev server:
199+
```bash
200+
kill $(lsof -t -i :<PORT>) 2>/dev/null
201+
```
202+
203+
Output a summary table:
141204

142205
```
143206
| Suite | Result |
@@ -150,6 +213,20 @@ mcp__iwsdk-dev-mcp__browser_get_console_logs(count: 30, level: ["error", "warn"]
150213
| 6. Stability | PASS/FAIL |
151214
```
152215

216+
If any suite fails, include which assertion failed and actual vs expected values.
217+
218+
---
219+
220+
## Recovery
221+
222+
If at any point a transient error occurs (server crash, WebSocket timeout, connection refused, etc.) that is NOT caused by a source code bug:
223+
1. Kill the dev server: `kill $(lsof -t -i :<PORT>) 2>/dev/null`
224+
2. Restart: re-run Step 2 to start a fresh dev server (port may change)
225+
3. Re-run the Pre-test Setup (reload, accept session)
226+
4. Retry the failed suite
227+
228+
Only give up after one retry attempt per suite. If the same suite fails twice, mark it FAIL and continue to the next suite.
229+
153230
---
154231

155232
## Known Issues & Workarounds
@@ -167,25 +244,10 @@ If `_stopRequested` and `_playRequested` are set simultaneously, stop wins.
167244
IWER runs in a browser context where the AudioContext may be suspended until a user gesture. The MCP tools can verify ECS state transitions but cannot confirm actual audio output.
168245

169246
### Audio example uses GLXF level
170-
The audio example loads entities from `./glxf/Composition.glxf`. Entities are not created in index.js — they come from the GLXF composition. The SpinSystem applies rotation to entities with AudioSource. Use `ecs_find_entities` to discover them dynamically.
247+
The audio example loads entities from `./glxf/Composition.glxf`. Entities are not created in index.js — they come from the GLXF composition. Use `ecs_find_entities` to discover them dynamically.
171248

172-
## Architecture Notes
173-
174-
### Playback State Machine
175-
```
176-
[AudioSource added] → _loading=true → loadAudio() → _loaded=true, _pool created
177-
↓ (_playRequested=true)
178-
handlePlaybackRequests():
179-
Restart: stop all → create new instance → _isPlaying=true
180-
Overlap: add instance (steal if full) → _isPlaying=true
181-
Ignore: skip if already playing
182-
FadeRestart: fade out current → fade in new → _isPlaying=true
183-
↓ (onended or _stopRequested)
184-
releaseInstance() → if no instances left → _isPlaying=false
185-
```
249+
### Boolean values must be JSON booleans
250+
When setting boolean fields (like `_playRequested`, `loop`, `_stopRequested`) via `ecs_set_component`, the `value` must be a JSON boolean (`true`), not a string (`"true"`). Strings silently fail.
186251

187-
### PlaybackMode Enum
188-
- `restart` — Stop current, play new
189-
- `overlap` — Add concurrent instance (up to maxInstances)
190-
- `ignore` — Skip if already playing
191-
- `fade-restart` — Crossfade from current to new
252+
### Entity indices change on reload
253+
Never cache entity indices across page reloads. Always re-discover via `ecs_find_entities`.

0 commit comments

Comments
 (0)