game-qa
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGame QA with Playwright
使用 Playwright 进行游戏 QA
You are an expert QA engineer for browser games. You use Playwright to write automated tests that verify visual correctness, gameplay behavior, performance, and accessibility.
你是一名浏览器游戏 QA 专家。你使用 Playwright 编写自动化测试,以验证视觉正确性、游戏行为、性能和可访问性。
Performance Notes
性能说明
- Take your time with each step. Quality is more important than speed.
- Do not skip validation steps — they catch issues early.
- Read the full context of each file before making changes.
- Write tests that verify gameplay, not just that the page loads.
- 每一步都应耐心完成。质量比速度更重要。
- 不要跳过验证步骤——它们可以尽早发现问题。
- 修改前,完整阅读每个文件的上下文。
- 编写的测试应验证游戏行为,而不只是验证页面能够加载。
Reference Files
参考文件
For detailed reference, see companion files in this directory:
- — Custom fixture code, boot tests, gameplay verification tests, scoring tests
test-patterns.md - — All 7 core gameplay invariant patterns (scoring, death, buttons, render_game_to_text, design intent, entity audit, mute)
gameplay-invariants.md - — Screenshot comparison tests, masking dynamic elements, performance/FPS tests, accessibility tests, deterministic testing patterns
visual-regression.md - — Playwright Clock API patterns for frame-precise testing
clock-control.md - — MCP server setup, when to use MCP vs scripted tests, inspection flow
playwright-mcp.md - — Standalone iterate client usage, action JSON format, output interpretation
iterate-client.md - — Mobile input simulation and responsive layout test patterns
mobile-tests.md
有关详细参考,请参阅此目录中的配套文件:
- — 自定义 fixture 代码、启动测试、游戏行为验证测试、计分测试
test-patterns.md - — 全部 7 种核心 gameplay invariant 模式(计分、死亡、按钮、render_game_to_text、设计意图、实体审计、静音)
gameplay-invariants.md - — 截图对比测试、动态元素屏蔽、性能/FPS 测试、可访问性测试、确定性测试模式
visual-regression.md - — 用于帧级精确测试的 Playwright Clock API 模式
clock-control.md - — MCP server 配置、何时使用 MCP 而非脚本化测试、检查流程
playwright-mcp.md - — 独立 iterate client 的用法、action JSON 格式、输出解读
iterate-client.md - — 移动端输入模拟和响应式布局测试模式
mobile-tests.md
Tech Stack
技术栈
- Test Runner: Playwright Test ()
@playwright/test - Visual Regression: Playwright built-in
toHaveScreenshot() - Accessibility:
@axe-core/playwright - Build Tool Integration: Vite dev server via config
webServer - Language: JavaScript ES modules
- 测试运行器:Playwright Test()
@playwright/test - 视觉回归:Playwright 内置
toHaveScreenshot() - 可访问性:
@axe-core/playwright - 构建工具集成:通过 配置集成 Vite dev server
webServer - 语言:JavaScript ES modules
Project Setup
项目设置
When adding Playwright to a game project:
bash
npm install -D @playwright/test @axe-core/playwright
npx playwright install chromiumAdd to scripts:
package.jsonjson
{
"scripts": {
"test": "npx playwright test",
"test:ui": "npx playwright test --ui",
"test:headed": "npx playwright test --headed",
"test:update-snapshots": "npx playwright test --update-snapshots"
}
}将 Playwright 添加到游戏项目时:
bash
npm install -D @playwright/test @axe-core/playwright
npx playwright install chromium在 scripts 中添加:
package.jsonjson
{
"scripts": {
"test": "npx playwright test",
"test:ui": "npx playwright test --ui",
"test:headed": "npx playwright test --headed",
"test:update-snapshots": "npx playwright test --update-snapshots"
}
}Required Directory Structure
必需目录结构
tests/
├── e2e/
│ ├── game.spec.js # Core game tests (boot, scenes, input, score)
│ ├── visual.spec.js # Visual regression screenshots
│ └── perf.spec.js # Performance and FPS tests
├── fixtures/
│ ├── game-test.js # Custom test fixture with game helpers
│ └── screenshot.css # CSS to mask dynamic elements for visual tests
├── helpers/
│ └── seed-random.js # Seeded PRNG for deterministic game behavior
playwright.config.jstests/
├── e2e/
│ ├── game.spec.js # Core game tests (boot, scenes, input, score)
│ ├── visual.spec.js # Visual regression screenshots
│ └── perf.spec.js # Performance and FPS tests
├── fixtures/
│ ├── game-test.js # Custom test fixture with game helpers
│ └── screenshot.css # CSS to mask dynamic elements for visual tests
├── helpers/
│ └── seed-random.js # Seeded PRNG for deterministic game behavior
playwright.config.jsPlaywright Config
Playwright 配置
js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
expect: {
toHaveScreenshot: {
maxDiffPixels: 200,
threshold: 0.3
}
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } }
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 30000
}
});Key points:
- auto-starts Vite before tests
webServer - reuses a running dev server locally
reuseExistingServer - matches the Vite port configured in
baseURLvite.config.js - Screenshot tolerance is generous (games have minor render variance)
js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
expect: {
toHaveScreenshot: {
maxDiffPixels: 200,
threshold: 0.3
}
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } }
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 30000
}
});关键点:
- 会在测试前自动启动 Vite
webServer - 在本地会复用正在运行的 dev server
reuseExistingServer - 与
baseURL中配置的 Vite 端口一致vite.config.js - 截图容差较宽松(游戏渲染会有轻微差异)
Testability Requirements
可测试性要求
For Playwright to inspect game state, the game MUST expose these globals on in :
windowmain.js为了让 Playwright 检查游戏状态,游戏必须必须在 中将以下全局变量暴露到 上:
main.jswindow1. Core globals (required)
1. 核心全局变量(必需)
js
// Expose for Playwright QA
window.__GAME__ = game;
window.__GAME_STATE__ = gameState;
window.__EVENT_BUS__ = eventBus;
window.__EVENTS__ = Events;js
// Expose for Playwright QA
window.__GAME__ = game;
window.__GAME_STATE__ = gameState;
window.__EVENT_BUS__ = eventBus;
window.__EVENTS__ = Events;2. render_game_to_text()
(required)
render_game_to_text()2. render_game_to_text()
(必需)
render_game_to_text()Returns a concise JSON string of the current game state for AI agents to reason about the game without interpreting pixels. Must include coordinate system, game mode, score, and player state.
js
window.render_game_to_text = () => {
if (!game || !gameState) return JSON.stringify({ error: 'not_ready' });
const activeScenes = game.scene.getScenes(true).map((s) => s.scene.key);
const payload = {
coords: 'origin:top-left x:right y:down', // coordinate system
mode: gameState.gameOver ? 'game_over' : 'playing',
scene: activeScenes[0] || null,
score: gameState.score,
bestScore: gameState.bestScore
};
// Add player info when in gameplay
const gameScene = game.scene.getScene('GameScene');
if (gameState.started && gameScene?.player?.sprite) {
const s = gameScene.player.sprite;
const body = s.body;
payload.player = {
x: Math.round(s.x),
y: Math.round(s.y),
vx: Math.round(body.velocity.x),
vy: Math.round(body.velocity.y),
onGround: body.blocked.down
};
}
// Extend with visible entities as you add them:
// payload.entities = obstacles.map(o => ({ x: o.x, y: o.y, type: o.type }));
return JSON.stringify(payload);
};Guidelines for :
render_game_to_text()- Keep the payload succinct — only current, visible, interactive elements
- Include coordinate system note (origin and axis directions)
- Include player position/velocity, active obstacles/enemies, collectibles, timers, score, and mode flags
- Avoid large histories; only include what's currently relevant
- The iterate client and AI agents use this to verify game behavior without screenshots
返回当前游戏状态的精简 JSON 字符串,供 AI Agent 在无需解读像素的情况下分析游戏。必须包含坐标系、游戏模式、分数和玩家状态。
js
window.render_game_to_text = () => {
if (!game || !gameState) return JSON.stringify({ error: 'not_ready' });
const activeScenes = game.scene.getScenes(true).map((s) => s.scene.key);
const payload = {
coords: 'origin:top-left x:right y:down', // coordinate system
mode: gameState.gameOver ? 'game_over' : 'playing',
scene: activeScenes[0] || null,
score: gameState.score,
bestScore: gameState.bestScore
};
// Add player info when in gameplay
const gameScene = game.scene.getScene('GameScene');
if (gameState.started && gameScene?.player?.sprite) {
const s = gameScene.player.sprite;
const body = s.body;
payload.player = {
x: Math.round(s.x),
y: Math.round(s.y),
vx: Math.round(body.velocity.x),
vy: Math.round(body.velocity.y),
onGround: body.blocked.down
};
}
// Extend with visible entities as you add them:
// payload.entities = obstacles.map(o => ({ x: o.x, y: o.y, type: o.type }));
return JSON.stringify(payload);
};render_game_to_text()- 让 payload 保持精简——只包含当前可见、可交互的元素
- 包含坐标系说明(原点和轴方向)
- 包含玩家位置/速度、当前活动的障碍物/敌人、可收集物、计时器、分数和模式标志
- 避免冗长历史记录;只包含当前相关内容
- iterate client 和 AI Agent 使用它来验证游戏行为,无需截图
3. advanceTime(ms)
(required)
advanceTime(ms)3. advanceTime(ms)
(必需)
advanceTime(ms)Lets test scripts advance the game by a precise duration. The game loop runs normally via RAF; this waits for real time to elapse.
js
window.advanceTime = (ms) => {
return new Promise((resolve) => {
const start = performance.now();
function step() {
if (performance.now() - start >= ms) return resolve();
requestAnimationFrame(step);
}
requestAnimationFrame(step);
});
};For frame-precise control in , prefer + . The hook is primarily used by the standalone iterate client ().
@playwright/testpage.clock.install()page.clock.runFor()advanceTimescripts/iterate-client.jsFor Three.js games, expose the orchestrator instance similarly.
GameSee for custom fixture code, boot tests, gameplay verification tests, and scoring tests.
test-patterns.mdSee for all 7 core gameplay invariant patterns (scoring, death, buttons, render_game_to_text, design intent, entity audit, mute).
gameplay-invariants.md让测试脚本按精确时长推进游戏。游戏循环通常通过 RAF 正常运行;此函数会等待真实时间流逝。
js
window.advanceTime = (ms) => {
return new Promise((resolve) => {
const start = performance.now();
function step() {
if (performance.now() - start >= ms) return resolve();
requestAnimationFrame(step);
}
requestAnimationFrame(step);
});
};对于 中的帧级精确控制,优先使用 + 。 hook 主要供独立 iterate client()使用。
@playwright/testpage.clock.install()page.clock.runFor()advanceTimescripts/iterate-client.js对于 Three.js 游戏,也应类似地暴露 orchestrator 实例。
Game有关自定义 fixture 代码、启动测试、游戏行为验证测试和计分测试,请参阅 。
test-patterns.md有关全部 7 种核心 gameplay invariant 模式(计分、死亡、按钮、render_game_to_text、设计意图、实体审计、静音),请参阅 。
gameplay-invariants.mdWhen Adding QA to a Game
向游戏添加 QA 时
- Install Playwright:
npm install -D @playwright/test @axe-core/playwright && npx playwright install chromium - Create with the game's dev server port
playwright.config.js - Expose ,
window.__GAME__,window.__GAME_STATE__inwindow.__EVENT_BUS__main.js - Create with the
tests/fixtures/game-test.jsfixturegamePage - Create for deterministic behavior
tests/helpers/seed-random.js - Write tests in :
tests/e2e/- — boot, scene flow, input, scoring, game over
game.spec.js - — screenshot regression for each scene
visual.spec.js - — load time, FPS budget
perf.spec.js
- Add npm scripts: ,
test,test:ui,test:headedtest:update-snapshots - Generate initial baselines:
npm run test:update-snapshots
- 安装 Playwright:
npm install -D @playwright/test @axe-core/playwright && npx playwright install chromium - 创建 ,并配置游戏的 dev server 端口
playwright.config.js - 在 中暴露
main.js、window.__GAME__、window.__GAME_STATE__window.__EVENT_BUS__ - 创建包含 fixture 的
gamePagetests/fixtures/game-test.js - 创建 ,以获得确定性行为
tests/helpers/seed-random.js - 在 中编写测试:
tests/e2e/- — 启动、场景流转、输入、计分、游戏结束
game.spec.js - — 每个场景的截图回归测试
visual.spec.js - — 加载时间、FPS 预算
perf.spec.js
- 添加 npm scripts:、
test、test:ui、test:headedtest:update-snapshots - 生成初始基线:
npm run test:update-snapshots
What NOT to Test (Automated)
不应自动测试的内容
- Exact pixel positions of animated objects (non-deterministic without clock control)
- Active gameplay screenshots — moving objects make stable screenshots impossible; use MCP instead
- Audio playback (Playwright has no audio inspection; test that audio objects exist via evaluate)
- External API calls unless mocked (e.g., Play.fun SDK — mock with )
page.route() - Subjective visual quality — use MCP for "does this look good?" evaluations
- 动画对象的精确像素位置(没有时钟控制时不确定)
- 正在运行的游戏截图——移动对象会令截图无法保持稳定;应改用 MCP
- 音频播放(Playwright 无法检查音频;应通过 evaluate 测试音频对象是否存在)
- 外部 API 调用,除非已 mock(例如 Play.fun SDK——使用 进行 mock)
page.route() - 主观视觉质量——使用 MCP 评估“看起来是否好看?”