Test Phaser games and canvas/WebGL applications with deterministic automation. Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y for Phaser 3 games. Use agent-browser CLI for browser automation, Vitest/Jest/RTL, flaky test triage, CI stabilization, and Phaser games needing deterministic input plus screenshot/state assertions. Trigger: "test phaser game", "phaser testing", "game testing", "canvas testing", "webgl testing", "test", "E2E", "flaky", "visual regression", "Playwright".
Test Phaser games and canvas/WebGL applications with deterministic automation. Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y for Phaser 3 games. Use agent-browser CLI for browser automation, Vitest/Jest/RTL, flaky test triage, CI stabilization, and Phaser games needing deterministic input plus screenshot/state assertions. Trigger: "test phaser game", "phaser testing", "game testing", "canvas testing", "webgl testing", "test", "E2E", "flaky", "visual regression", "Playwright".
Phaser Game Testing
Test Phaser 3 games reliably: enable safe refactors by choosing the right test layer, making canvas/WebGL games observable, and eliminating nondeterminism so failures are actionable.
Philosophy: Confidence Per Minute
Frontend tests fail for two reasons: the product is broken, or the test is lying. Your job is to maximize signal and minimize "test is lying".
Before writing a test, ask:
What user risk am I covering (money, progression, auth, data loss, crashes)?
What's the narrowest layer that catches this bug class (pure logic vs UI vs full browser)?
What "ready" signal can I wait on besides setTimeout?
What should a failure print/screenshot so it's diagnosable in CI?
Core principles:
Test the contract, not the implementation: assert stable user-meaningful outcomes and public seams.
Prefer determinism over retries: make time/RNG/network controllable; remove flake at the source.
Observe like a debugger: console errors, network failures, screenshots, and state dumps on failure.
One critical flow first: a reliable smoke test beats 50 flaky tests.
Unit Testing for Pure Logic
: Is this pure logic? → Use unit tests, not browser automation.
Decision Tree
For pure logic utilities (maze generation, score sorting, storage, math algorithms), use Vitest for fast, deterministic unit tests. Reserve browser automation (agent-browser) for integration contracts and UI flows.
What Should Be Unit Tested
✅ Maze generation algorithms - Deterministic with seeded RNG
✅ Score sorting/validation - Pure data transformations
Critical user flows across routing, storage, real bundling/runtime
Visual
Specialized
Layout/pixel regressions; for canvas/WebGL, only after locking determinism
Quick Start: First Smoke Test
Define 1 critical flow: "page loads → user can start → one key action works"
Add a test seam to the app (see below)
Choose runner: agent-browser CLI for E2E, unit tests for logic
Fail loudly: treat console errors and failed requests as test failures
Stabilize: seed RNG, freeze time, fix viewport, disable animations
Concrete agent-browser Workflow: Testing a Game
Step-by-step sequence for testing a Phaser/canvas game:
Important: For Phaser games, skip snapshot -i and use window.__TEST__ directly.
1. agent-browser open http://localhost:3000?test=1&seed=42
2. agent-browser eval "new Promise(r => { const c = () => window.__TEST__?.ready ? r(true) : setTimeout(c, 100); c(); })"
(Wait for game ready)
3. agent-browser errors
(Fail if any errors)
4. agent-browser eval "window.__TEST__.commands.goToScene('GameScene')"
(Use test seam commands instead of DOM clicks)
5. agent-browser eval "window.__TEST__.gameState()"
(Assert game state is correct)
6. agent-browser press ArrowRight
(Or WASD for movement)
7. agent-browser eval "window.__TEST__.gameState().player.x"
(Verify movement happened)
8. agent-browser screenshot gameplay-state.png
(Visual evidence after deterministic setup)
Note: For DOM-based UI (menus, buttons), you may still use snapshot -i and click @e1, but prefer test seam commands when available.
Standardized Test Seam Pattern
Important: Agents should skip snapshot -i for Phaser games and go directly to window.__TEST__. The test seam provides all necessary access without DOM inspection.
TestManager Singleton Pattern
Use a centralized TestManager singleton for consistent test seam management across all scenes:
Rule: Expose IDs + essential fields, not raw Phaser/engine objects.
Note: Prefer the TestManager pattern above for new projects.
Anti-Patterns to Avoid
❌ Testing the wrong layer: E2E tests for pure logic
Why tempting: "Let's just test everything through the browser"
Better: Unit tests for logic; reserve E2E for integration contracts
❌ Testing implementation details: Asserting DOM structure/classnames
Why tempting: Easy to assert what you can see in DevTools
Better: Assert user-meaningful outputs (text, score, HP changes)
❌ Sleep-driven tests: wait 2s then clickWhy tempting: Simple and "works on my machine"
Better: Wait on explicit readiness (DOM marker, window.__TEST__.ready)
❌ Uncontrolled randomness: RNG/time in assertions
Why tempting: "The game uses random, so the test should too"
Better: Seed RNG (?seed=42), freeze time, assert stable invariants
❌ Pixel snapshots without determinism: Canvas screenshots that flake
Why tempting: "I'll catch visual bugs automatically"
Better: Deterministic mode first; then screenshot at known stable frames
❌ Retries as a strategy: "Just bump retries to 3"
Why tempting: Quick fix that makes CI green
Better: Fix the flake source; retries hide real problems
Debugging Failed Tests
When a test fails, gather evidence in this order:
Console errors: agent-browser errors or agent-browser console
Network failures: agent-browser network requests → check for non-2xx
Screenshot: agent-browser screenshot failure-state.png → visual state at failure
Canvas UI issues (panel seams, segmented ribbons, invisible HUD fills) are best caught with a dedicated UI harness instead of the full gameplay flow.
Build a simple test.html/scene that loads only the UI assets.
Render raw slices next to assembled panels (multi-size), and include ribbon/bars with both “raw crop + scale” and “stitched multi-slice” views.
Expose window.__TEST__ with .commands.showTest(n) so agent-browser can toggle each mode deterministically.
Capture targeted screenshots (panels, ribbons, bars) and diff them in CI.
See references/phaser-canvas-testing.md for the deterministic setup + screenshot workflow.
For general Phaser UI components (not just slicing), use the same idea via standalone component test scenes (phaser-component-test-scenes skill): one scene per component, test via ?scene=ComponentNameTestScene.
Direct Scene Access for Testing
For testing specific scenes without navigating the full game flow, use URL parameters to start directly at a scene.
// main.tsconst params = newURLSearchParams(window.location.search);
const sceneParam = params.get('scene');
const isTestMode = params.has('test');
const seedParam = params.get('seed');
constconfig: Phaser.Types.Core.GameConfig = {
// ... other configscene: sceneParam
? [sceneParam] // Start directly at specified scene
: [BootScene, PreloaderScene, MenuScene, GameScene], // Normal flow
};
const game = newPhaser.Game(config);
// If test mode, initialize with seedif (isTestMode && seedParam) {
const seed = parseInt(seedParam);
seedRNG(seed);
window.__TEST__.seed = seed;
}
TestManager Integration
// In TestManagercommands: {
goToScene: (key, data) => {
const game = this.getCurrentScene()?.scene?.game;
if (game) {
game.scene.start(key, data);
}
}
}
Agent Workflow
# Test specific scene directly
agent-browser open http://localhost:3000?scene=GameScene&test=1&seed=42
# Or navigate via test seam
agent-browser eval"window.__TEST__.commands.goToScene('GameScene', { level: 1 })"
Workflow: For testing specific scenes, use ?scene=SceneName instead of navigating full game flow.
Variation Guidance
Adapt approach based on context:
DOM app: Standard agent-browser selectors, wait for text/elements
Canvas game: Test seams mandatory, wait via window.__TEST__.ready
Hybrid: DOM for menus, test seams for gameplay
CI-only GPU: May need software rendering flags or skip visual tests
UI slicing regressions: For nine-slice/ribbon/bar artifacts, prefer a small UI harness scene/page with deterministic modes and targeted screenshots (references/phaser-canvas-testing.md).
Test Seam Discovery
Always check for window.__TEST__ before DOM interactions
Test seams are PRIMARY method for Phaser game testing:
Each scene creates its own test seam in create() method
Test seam sceneKey may lag on scene transitions (use console logs as fallback)
Check source code for available test seam commands
Document discovered commands in progress.txt
Standard Readiness Check Patterns
Use exponential backoff for test seam discovery:
# Standard readiness check with exponential backoffwait_for_test_seam() {
local max_attempts=5
local attempt=0
while [ $attempt -lt $max_attempts ]; dolocal delay=$((2 ** $attempt)) # 1s, 2s, 4s, 8s, 16ssleep$delayif agent-browser eval"typeof window.__TEST__ !== 'undefined' && typeof window.__TEST__.commands !== 'undefined'"; thenecho"Test seam ready"return 0
fi
attempt=$((attempt + 1))
doneecho"Test seam not available after $max_attempts attempts"return 1
}
More reliable (readiness flags may not be reliable)
Simpler code (no Promise chains)
Faster execution (no async overhead)
Readiness Flag Reliability
Important: window.__TEST__?.ready may not be reliable. Prefer direct property checks:
# ✅ PREFERRED: Direct property check
agent-browser eval"window.__TEST__?.sceneKey || false"
agent-browser eval"Object.keys(window.__TEST__?.commands || {}).length > 0"# ⚠️ LESS RELIABLE: Readiness flag
agent-browser eval"window.__TEST__?.ready || false"
When Readiness Flags May Not Work:
Scene transitions (flag may lag)
Complex initialization (flag may not update)
Test seam setup issues (flag may not be set)
Solution: Use direct property checks instead
Timeout Handling
Include timeout handling (max 5 seconds) before fallback:
# Timeout-based check with direct property accesscheck_test_seam_with_timeout() {
local max_wait=5 # 5 seconds maxlocal elapsed=0
while [ $elapsed -lt $max_wait ]; doif agent-browser eval"window.__TEST__?.sceneKey || false"; thenecho"Test seam available"return 0
fisleep 1
elapsed=$((elapsed + 1))
doneecho"Test seam not available after $max_wait seconds"return 1
}
If test seam isn't available after timeout:
Document limitation in progress.txt
Proceed with alternative verification (code review, TypeScript compilation)
Don't wait indefinitely - test seams may not be available in all contexts
Framework-Specific Command References
Common test seam commands by framework:
Phaser 3:
window.__TEST__.commands.goToScene(key, data)
window.__TEST__.commands.gameState()
window.__TEST__.commands.setTimer(seconds)
window.__TEST__.sceneKey (current scene)
React/Web Apps:
window.__TEST__.commands.navigate(route)
window.__TEST__.commands.getState()
window.__TEST__.currentRoute
Graceful Degradation:
If test seams unavailable: Use DOM inspection or code review
Document limitation: Note why test seam wasn't used
Alternative verification: TypeScript compilation, code review
Coordinate System Documentation
World Coordinates vs Screen Coordinates
Understanding Phaser coordinate systems is critical for UI positioning tasks.
World Coordinates:
Game world space (e.g., 800x600 game world)
Camera-independent (objects exist in world space)
Used for game objects, sprites, physics bodies
Example: sprite.x = 400 (400 pixels from world origin)
Screen Coordinates:
Viewport/camera space (what player sees)
Camera-dependent (changes with camera scroll)
Used for UI elements, HUD, overlays
Example: ui.x = 400 (400 pixels from screen origin)
Key Difference:
// World coordinates (game object)
sprite.x = 400; // 400 pixels in world space// Screen coordinates (UI element)
ui.x = 400; // 400 pixels from screen edge (camera-independent)
Camera Scroll Offset Handling
When calculating positions, account for camera scroll:
// ❌ WRONG: Not accounting for camera scrollconst worldX = 400;
sprite.x = worldX; // May be off-screen if camera scrolled// ✅ CORRECT: Account for camera scrollconst cameraX = this.cameras.main.scrollX;
const worldX = 400;
sprite.x = cameraX + worldX; // Correct position relative to camera
For UI Elements (Screen Coordinates):
// UI elements use screen coordinates (camera-independent)
ui.x = 400; // Always 400 pixels from screen edge, regardless of camera
Position Calculation Patterns
Pattern 1: Center Text on Screen
// Calculate text width firstconst textWidth = text.width;
const screenWidth = this.cameras.main.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX; // Center text horizontally
// Sprite origin affects position calculation
sprite.setOrigin(0.5, 0.5); // Center origin
sprite.x = 400; // Center of sprite at x=400// If origin is (0, 0), sprite.x is top-left corner
sprite.setOrigin(0, 0);
sprite.x = 400; // Top-left corner at x=400
Common Gotchas About Position Calculations
Gotcha 1: Not Accounting for Text Width
// ❌ WRONG: Assuming fixed width
text.x = 400; // May not be centered if text width varies// ✅ CORRECT: Calculate based on actual widthconst textWidth = text.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Gotcha 2: Confusing World vs Screen Coordinates
// ❌ WRONG: Using world coordinates for UI
ui.x = sprite.x; // UI will move with camera scroll// ✅ CORRECT: Use screen coordinates for UI
ui.x = 400; // UI stays fixed on screen
Gotcha 3: Not Accounting for Origin
// ❌ WRONG: Assuming origin is (0, 0)
sprite.x = 100; // May not be where expected if origin is (0.5, 0.5)// ✅ CORRECT: Account for origin
sprite.setOrigin(0.5, 0.5);
sprite.x = 100; // Center of sprite at x=100
WebGL Warning Handling
Known Non-Critical WebGL Warnings
Some WebGL warnings are non-critical and can be ignored:
Warning: WebGL context lost
When to ignore: During development, if game still works
When to investigate: If game stops working or performance degrades
Common cause: Browser resource limits, GPU driver issues
Warning: Texture size exceeds maximum
When to ignore: If texture is automatically scaled down
When to investigate: If texture quality is unacceptable
# Check if WebGL context is active
agent-browser eval"
const game = window.__TEST__?.getCurrentScene()?.scene?.game;
if (game) {
const renderer = game.renderer;
renderer.gl ? 'WebGL active' : 'Canvas2D fallback'
} else {
'Game not initialized'
}
"
Sprite Origin Adjustments
Sprite Textures May Not Be Visually Centered
Important: Sprite textures may not be visually centered even with origin (0.5, 0.5).
Why This Happens:
Texture has transparent padding
Texture has uneven padding
Texture dimensions don't match visual content
Solution: Adjust origin or reposition sprite
Origin Adjustment Patterns
Pattern 1: Fine-Tune Origin
// Standard center origin
sprite.setOrigin(0.5, 0.5);
// Fine-tune if visually off-center
sprite.setOrigin(0.4, 0.5); // Slightly left of center
sprite.setOrigin(0.6, 0.5); // Slightly right of center
Pattern 2: Adjust Position Instead
// If origin adjustment doesn't work, adjust position
sprite.setOrigin(0.5, 0.5);
sprite.x = targetX + offsetX; // Add offset to compensate
sprite.y = targetY + offsetY;
// ✅ CORRECT: Adjust origin for consistent offset
sprite.setOrigin(0.4, 0.5); // All sprites use this origin// ✅ CORRECT: Reposition for precise placement
sprite.setOrigin(0.5, 0.5);
sprite.x = targetX + 5; // 5 pixel offset for this sprite
Common Patterns
Pattern 1: Successful UI Layout Calculation
Example from real task:
// Calculate text width firstconst textWidth = this.add.text(0, 0, "Score: 100", style).width;
const screenWidth = this.cameras.main.width;
// Center text horizontallyconst centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
// Position below with spacingconst spacing = 20;
button.y = text.y + text.height + spacing;
Key Points:
Calculate text width before positioning
Account for screen width (not world width)
Use spacing constants for consistency
Pattern 2: Successful Coordinate System Usage
Example from real task:
// World coordinates for game object
player.x = 400; // 400 pixels in world space// Screen coordinates for UI
scoreText.x = 100; // 100 pixels from screen edge (camera-independent)// Account for camera scroll when neededconst cameraX = this.cameras.main.scrollX;
enemy.x = cameraX + 500; // 500 pixels ahead of camera
Key Points:
Use world coordinates for game objects
Use screen coordinates for UI
Account for camera scroll when needed
Pattern 3: Successful Origin Handling
Example from real task:
// Set origin first
sprite.setOrigin(0.5, 0.5);
// Position at target
sprite.x = 400;
sprite.y = 300;
// Fine-tune if visually off-centerif (sprite appears off-center) {
sprite.setOrigin(0.4, 0.5); // Adjust origin// OR
sprite.x += 5; // Adjust position
}
Key Points:
Set origin before positioning
Fine-tune if visually off-center
Use origin adjustment or position offset
Troubleshooting: Coordinate Confusion
Problem: UI Element Not Where Expected
Symptoms:
UI element appears in wrong location
UI element moves with camera scroll
UI element position changes unexpectedly
Diagnosis:
Check if using world vs screen coordinates
Verify origin is set correctly
Check if camera scroll is affecting position
Solution:
// For UI elements, use screen coordinates
ui.x = 100; // Screen coordinate (camera-independent)// If using world coordinates, account for cameraconst cameraX = this.cameras.main.scrollX;
ui.x = cameraX + 100; // World coordinate (camera-dependent)
Problem: Text Not Centered
Symptoms:
Text appears off-center
Text position changes with text content
Diagnosis:
Check if text width is calculated
Verify center calculation is correct
Check if origin is set correctly
Solution:
// Calculate text width firstconst textWidth = text.width;
const screenWidth = this.cameras.main.width;
// Center calculationconst centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
// Set origin to left (default for text)
text.setOrigin(0, 0.5); // Left-aligned, vertically centered
Problem: Sprite Position Wrong After Origin Change
Symptoms:
Sprite moves when origin is changed
Sprite position doesn't match expected location
Diagnosis:
Origin change affects position calculation
Position needs adjustment after origin change
Solution:
// Set origin first
sprite.setOrigin(0.5, 0.5);
// Then set position
sprite.x = 400;
sprite.y = 300;
// If position is wrong, adjust
sprite.x += offsetX;
sprite.y += offsetY;
Complete Test Seam Command Reference by Scene
Reference: See phaser-test-seam-patterns skill for comprehensive command catalog.
MainMenu Scene
clickStartGame() - Navigate to GameScene
clickHighScores() - Navigate to HighScoresScene
clickSettings() - Navigate to SettingsScene
GameScene
setTimer(seconds) - Set timer to specific value
fastForwardTimer(seconds) - Fast forward timer
triggerGameOver() - Force game over state
movePlayerTo(x, y) - Move player to position
movePlayerToExit() - Move player to exit (complete level)
collectAnyCoin() - Collect nearest coin
collectCoin(x, y) - Collect coin at position
gameState() - Get current game state (score, timer, player position)
GameOverScene
clickPlayAgain() - Restart game
clickMainMenu() - Navigate to MainMenu
getFinalScore() - Get final score
HighScoresScene
clickMainMenu() - Navigate to MainMenu
getHighScores() - Get high scores list
Common Commands (All Scenes)
goToScene(key, data) - Navigate to any scene
gameState() - Get current game state
reset() - Reset game state
seed(n) - Set RNG seed
Command Discovery Patterns:
Check scene create() method for window.__TEST__.commands definition
Look for test seam setup in scene files
Check for TestManager singleton pattern (centralized commands)
Document scene-specific commands in progress.txt
Scene Navigation Workflows:
# Navigate from MainMenu to GameScene
agent-browser eval"window.__TEST__.commands.clickStartGame()"# Wait for transition
agent-browser wait 2000
# Verify scene change (use console logs as fallback)
agent-browser console
# Navigate directly to scene
agent-browser eval"window.__TEST__.commands.goToScene('GameScene', { level: 1 })"
Common Testing Scenario Templates:
Scenario 1: Test Game Flow
# Start at MainMenu
agent-browser open http://localhost:3000?scene=MainMenu&test=1&seed=42
# Navigate to game
agent-browser eval"window.__TEST__.commands.clickStartGame()"
agent-browser wait 2000
# Set timer for quick testing
agent-browser eval"window.__TEST__.commands.setTimer(5)"# Collect coin
agent-browser eval"window.__TEST__.commands.collectAnyCoin()"# Verify score updated
agent-browser eval"window.__TEST__.gameState().score"
Scenario 2: Test Game Over
# Start at GameScene
agent-browser open http://localhost:3000?scene=GameScene&test=1&seed=42
# Trigger game over
agent-browser eval"window.__TEST__.commands.triggerGameOver()"
agent-browser wait 2000
# Verify GameOverScene
agent-browser eval"window.__TEST__.sceneKey"# Test play again
agent-browser eval"window.__TEST__.commands.clickPlayAgain()"
Test Seam Debugging Patterns:
If command not found: Check scene source code for command definition
If sceneKey doesn't update: Use console logs as fallback verification
If command fails: Check if scene is initialized (wait for window.__TEST__.ready)
If navigation fails: Verify scene key spelling matches Phaser scene registration
Scene Transition Testing
Use test seam commands for navigation:
clickStartGame() - Navigate to game
clickPlayAgain() - Restart game
goToScene(key, data) - Navigate to any scene directly
Scene Navigation Patterns
Pattern 1: Direct Scene Navigation
# Navigate directly to scene (preferred)
agent-browser eval"window.__TEST__.commands.goToScene('GameScene', { level: 1 })"
agent-browser wait 500 # Minimal wait for transition
agent-browser eval"window.__TEST__.sceneKey === 'GameScene'"
Pattern 2: Navigation via UI Commands
# Navigate via UI command
agent-browser eval"window.__TEST__.commands.clickStartGame()"
agent-browser wait 500 # Minimal wait for transition# Verify with console logs (fallback if sceneKey lags)
agent-browser console
Pattern 3: Scene Navigation with Retry
# Navigate with retry on failurenavigate_to_scene() {
local scene=$1local max_attempts=3
local attempt=0
while [ $attempt -lt $max_attempts ]; do
agent-browser eval"window.__TEST__.commands.goToScene('$scene')"
agent-browser wait 500
if agent-browser eval"window.__TEST__.sceneKey === '$scene'"; thenecho"Successfully navigated to $scene"return 0
fi
attempt=$((attempt + 1))
sleep $((2 ** $attempt)) # Exponential backoffdoneecho"Failed to navigate to $scene after $max_attempts attempts"return 1
}
Wait patterns:
Wait 500ms after transition (minimal wait, not 2 seconds)
Use console logs to verify scene transitions
Don't rely solely on test seam sceneKey (known to lag)
Retry navigation with exponential backoff if needed
Pattern:
# Navigate to game
agent-browser eval"window.__TEST__.commands.clickStartGame()"# Wait for transition (minimal wait)
agent-browser wait 500
# Verify with console logs (fallback)
agent-browser console
Timer Testing
Use test seam setTimer(seconds) for direct manipulation
Never wait for natural countdown - use timer manipulation:
Set timer to low value (e.g., 3 seconds) for quick testing
Test boundary conditions (9, 10, 11 seconds for color changes)
Add triggerGameOver() command for testing
Pattern:
# Set timer to 5 seconds
agent-browser eval"window.__TEST__.commands.setTimer(5)"# Fast forward timer
agent-browser eval"window.__TEST__.commands.fastForwardTimer(10)"# Trigger game over
agent-browser eval"window.__TEST__.commands.triggerGameOver()"
Movement Testing (Enhanced)
Phaser requires keydown/keyup pattern, not single press
Scale Manager is active: scale.scaleMode !== Phaser.Scale.NONE
Aspect ratio preserved: For FIT mode, game maintains aspect ratio
Auto-centering works: Game is centered in viewport
Resize events handled: Window resize updates game size correctly
No manual CSS scaling: Canvas element has no transform or percentage width/height
Common Scaling Issues to Test
Game too small: Verify scale mode and base dimensions
Game distorted: Check aspect ratio preservation
Game off-center: Verify autoCenter configuration
Input misaligned: Indicates manual scaling breaking coordinate system
Resize not working: Check Scale Manager event handling
Browser Testing Optimization
Batch related commands:
// Batch independent checks in single eval
agent-browser eval"
const state = window.__TEST__.gameState();
JSON.stringify({
score: state.score,
timer: state.timer,
playerX: state.player?.x,
playerY: state.player?.y
})
"
Use parallel evaluation where possible:
// Use Promise.all() for parallel checks
agent-browser eval"
Promise.all([
Promise.resolve(window.__TEST__.gameState().score),
Promise.resolve(window.__TEST__.gameState().timer)
]).then(results => ({ score: results[0], timer: results[1] }))
"
Reduce wait times between commands:
# Use minimal waits (500ms for scene transitions)
agent-browser eval"window.__TEST__.commands.goToScene('GameScene')"
agent-browser wait 500 # Not 2000ms
Remember
You can make almost any frontend (including canvas/WebGL games) testable by adding a tiny, stable seam for readiness + state. One reliable smoke test is the foundation. Aim for tests that are boring to maintain: deterministic, explicit about readiness, and rich in failure evidence. The goal is confidence, not coverage numbers.