# AI Game Playtester

> 【AI 自动化游戏测试】当需要自动运行游戏、检测崩溃和异常、验证关卡可通关性、生成质量报告时使用。适用场景：冒烟测试、关卡可达性验证、性能基准测试、回归测试、碰撞检测验证、UI 交互测试。触发词：测试游戏、跑测试、冒烟测试、回归测试、自动测试、检查崩溃、性能测试、验证关卡、playtest、smoke test、regression test、run tests、QA check、验收测试。⚠️测试过程中严禁修改游戏源码，发现问题只记录不修复

- Skill: `gitcustomer/ai-game-playtester` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gitcustomer/ai-game-playtester`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gitcustomer/ai-game-playtester/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: gitcustomer (https://skillmd.com/u/gitcustomer)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/gitcustomer/ai-game-playtester

---


# AI Game Playtester

AI 驱动的自动化游戏测试执行器。启动游戏实例，模拟玩家行为，收集运行指标，输出结构化质量报告。

## ⚠️ Hard Rules

1. **测试过程中严禁修改游戏源码** — Playtester 的职责是"发现问题"而非"修复问题"。所有发现的缺陷写入报告，由开发者或其他 Skill 决定是否修复
2. **必须在隔离环境中运行** — 测试运行不得影响用户的开发环境。使用独立的预览实例或沙箱进程，不得占用用户正在使用的编辑器或预览端口
3. **超时必须强制终止** — 每个测试用例有硬性超时（默认 60 秒），超时视为 FAIL 并记录，防止死循环或卡死导致测试永远挂起
4. **截图/日志必须伴随结论** — 每条 PASS/FAIL 判定必须附带可追溯的证据（终端输出片段、错误日志行号、截图路径），禁止无证据标记 PASS
5. **性能数据必须连续采样** — FPS、内存等性能指标必须以 ≥1Hz 频率连续采样至少 10 秒，禁止只取单点快照。单点快照无法反映卡顿和内存泄漏
6. **测试报告必须机器可解析** — 同时输出 JSON（机器消费）和 Markdown（人类阅读）两种格式

## 触发条件

当用户说以下内容时触发本 Skill：
- "测试一下游戏" / "跑个冒烟测试"
- "检查有没有崩溃" / "验证关卡能不能通关"
- "跑回归测试" / "性能测试"
- "run playtest" / "smoke test" / "QA check"

## 不触发条件

以下情况应使用其他 Skill：
- 用户想修复发现的 bug → 直接编辑代码或使用开发 Skill
- 用户想生成新关卡 → 使用 `ai-game-level-generator`
- 用户想导入/生成素材 → 使用 `ai-game-asset-pipeline`
- 用户想编写单元测试代码 → 使用项目的测试框架

## 输入参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `test_suite` | string | | `smoke` | 测试集：`smoke`（冒烟）/ `regression`（回归）/ `performance`（性能）/ `full`（全量） |
| `scene` | string | | 主场景 | 要测试的场景/关卡路径 |
| `timeout_seconds` | number | | `60` | 单用例超时秒数 |
| `report_dir` | string | | `.playtest/reports` | 报告输出目录 |
| `capture_screenshots` | boolean | | `true` | 是否在关键节点截图 |

## 测试集定义

### Smoke（冒烟测试）— 快速验证核心链路

| TC-ID | 用例名称 | 步骤 | 期望结果 | 超时 |
|-------|---------|------|---------|------|
| SM-01 | 游戏启动 | 启动游戏进程 | 进程存活，无崩溃日志 | 15s |
| SM-02 | 主场景加载 | 等待主场景加载完成 | 场景根节点存在，无加载错误 | 30s |
| SM-03 | 基础渲染 | 检查首帧渲染 | FPS > 0，画面非全黑/全白 | 10s |
| SM-04 | 输入响应 | 模拟键盘/触摸输入 | 游戏对象产生位移或状态变化 | 15s |
| SM-05 | 无致命错误 | 扫描运行日志 | 无 ERROR/FATAL 级别日志 | 5s |

### Regression（回归测试）— 验证已有功能未被破坏

| TC-ID | 用例名称 | 步骤 | 期望结果 | 超时 |
|-------|---------|------|---------|------|
| RG-01 | 场景切换 | 遍历所有已注册场景 | 每个场景均能加载，无崩溃 | 120s |
| RG-02 | 碰撞检测 | 移动玩家撞向墙壁/地面 | 不穿模，不卡墙 | 30s |
| RG-03 | 音频播放 | 触发 BGM 和音效 | 音频播放器状态为 playing（非静音/非暂停） | 15s |
| RG-04 | UI 交互 | 点击所有可见按钮 | 按钮触发对应回调，无未处理异常 | 30s |
| RG-05 | 存档加载 | 保存后立即加载 | 玩家位置/状态与保存时一致 | 20s |
| RG-06 | 资源完整性 | 扫描所有引用资源 | 无 missing resource 警告 | 30s |

### Performance（性能测试）— 采集运行时指标

| TC-ID | 指标 | 采样方式 | 通过阈值 | 告警阈值 |
|-------|------|---------|---------|---------|
| PF-01 | FPS | 连续 10 秒，1Hz 采样 | avg ≥ 30, min ≥ 15 | avg < 30 或 min < 15 |
| PF-02 | 内存 | 连续 30 秒，1Hz 采样 | 增长 < 10MB | 增长 ≥ 10MB（疑似泄漏） |
| PF-03 | 启动耗时 | 从进程启动到首帧 | < 5s | ≥ 5s |
| PF-04 | 场景切换耗时 | 从调用到加载完成 | < 2s | ≥ 2s |
| PF-05 | Draw Calls | 主场景稳态 | < 500 | ≥ 500 |

## 执行流程

### 步骤 0：前置检查

1. **检查游戏项目是否存在**：
   - 扫描项目根目录，确认存在引擎配置文件或可运行的构建产物
   - 若找不到任何可识别的游戏项目 → 报错并停止
2. **检查构建产物是否就绪**：
   - Web 游戏：检查是否存在 `index.html` 或类似入口文件
   - 桌面游戏：检查是否存在可执行文件或引擎项目文件
   - 不存在 → 提示用户先执行构建/导出
3. **创建报告输出目录**：`{report_dir}/` 不存在时自动创建

### 步骤 1：启动测试环境

在隔离环境中启动游戏实例。根据项目类型选择启动方式：

**Web 游戏**：
```
启动本地 HTTP Server（选用未占用的端口，如 18080-18099）
  → 加载游戏页面（index.html）
  → 等待 window.onload 或引擎 ready 信号
  → 确认渲染帧数 > 0
```

**桌面游戏**：
```
启动导出的可执行文件 或 引擎的命令行运行模式
  → 捕获 stdout/stderr
  → 等待主场景初始化完成
```

**启动失败判定**：
- 进程在 15 秒内退出（exit code ≠ 0）→ FAIL，记录 stderr
- 进程启动但 30 秒内无渲染帧 → FAIL，记录日志

### 步骤 2：执行测试用例

按 `test_suite` 展开对应的测试集，逐条执行：

**执行逻辑**：

```
FOR EACH test_case IN test_suite:
  1. 记录开始时间
  2. 设置超时计时器（timeout_seconds）
  3. 执行测试步骤（模拟输入 / 读取状态 / 检查日志）
  4. 对比期望结果
  5. 若 capture_screenshots == true → 在关键节点截图
  6. 记录结果：PASS / FAIL / TIMEOUT / BLOCKED
  7. 记录证据：日志片段 / 截图路径 / 具体数值
  超时 → 标记 TIMEOUT，强制终止当前用例，继续下一条
```

**输入模拟方式**：

| 游戏平台 | 输入模拟方法 |
|---------|------------|
| Web 游戏 | JavaScript `dispatchEvent(new KeyboardEvent(...))` / Puppeteer `page.keyboard` |
| 桌面游戏 | 引擎 CLI 命令行输入注入 / 操作系统级键鼠模拟 |
| 移动游戏 | 触摸事件模拟框架（如 Appium） |

**状态检测方式**：

| 检测项 | Web 游戏 | 桌面游戏 |
|--------|---------|---------|
| 元素/节点存在 | `document.querySelector()` / Canvas 状态检查 | 引擎调试接口 / 进程状态查询 |
| FPS | `requestAnimationFrame` 计数 / `performance.now()` 差值 | 引擎性能 API / 外部帧率检测工具 |
| 内存 | `performance.memory`（Chrome 限定）/ `performance.measureUserAgentSpecificMemory()` | 操作系统进程内存查询（如 `ps`、`tasklist`） |
| 日志错误 | `window.onerror` / `console.error` 钩子 | 捕获 stdout/stderr，正则匹配 `ERROR\|FATAL\|EXCEPTION` |
| 音频状态 | `HTMLAudioElement.paused == false` / Web Audio API 状态 | 引擎音频 API / 系统音频流检测 |

### 步骤 3：采集性能数据

性能测试单独执行（不与功能测试混合，避免互相干扰）：

```
1. 启动游戏到主场景稳态
2. 等待 3 秒使初始化完成
3. 开始连续采样（采样间隔 1 秒）
4. 采样 10-30 秒
5. 计算统计值：avg / min / max / p95 / stddev
6. 与阈值对比，输出判定
```

**性能数据结构**：

```json
{
  "tc_id": "PF-01",
  "metric": "fps",
  "samples": [58, 60, 59, 45, 60, 60, 58, 60, 30, 60],
  "statistics": {
    "avg": 55.0,
    "min": 30,
    "max": 60,
    "p95": 60,
    "stddev": 9.2
  },
  "threshold": { "avg_gte": 30, "min_gte": 15 },
  "verdict": "PASS"
}
```

### 步骤 4：生成报告

在 `report_dir` 下同时输出两种格式的报告：

**4.1 JSON 报告** — `playtest-{timestamp}.json`

```json
{
  "schema_version": "1.0",
  "test_suite": "smoke",
  "timestamp": "2026-05-07T17:30:00Z",
  "environment": {
    "engine": "your-engine-name",
    "engine_version": "x.x",
    "platform": "web",
    "project_root": "/path/to/project"
  },
  "summary": {
    "total": 5,
    "pass": 4,
    "fail": 0,
    "timeout": 1,
    "blocked": 0,
    "pass_rate": "80%"
  },
  "results": [
    {
      "tc_id": "SM-01",
      "name": "游戏启动",
      "verdict": "PASS",
      "duration_ms": 2340,
      "evidence": "进程 PID 12345 正常启动，exit_code=null（仍在运行）"
    },
    {
      "tc_id": "SM-04",
      "name": "输入响应",
      "verdict": "TIMEOUT",
      "duration_ms": 60000,
      "evidence": "模拟 KEY_RIGHT 后 60 秒内 Player.position.x 无变化，疑似输入未绑定"
    }
  ],
  "performance": [],
  "screenshots": [
    ".playtest/reports/screenshots/SM-02_main_scene_loaded.png",
    ".playtest/reports/screenshots/SM-04_input_no_response.png"
  ]
}
```

**4.2 Markdown 报告** — `playtest-{timestamp}.md`

```markdown
# 🎮 Playtest 报告

> 测试时间: 2026-05-07 17:30 | 引擎: your-engine x.x | 测试集: Smoke

## 📊 总览

| 指标 | 值 |
|------|-----|
| 总用例 | 5 |
| ✅ PASS | 4 |
| ❌ FAIL | 0 |
| ⏱️ TIMEOUT | 1 |
| 通过率 | 80% |

## 📝 详细结果

| TC-ID | 用例 | 结果 | 耗时 | 证据 |
|-------|------|------|------|------|
| SM-01 | 游戏启动 | ✅ PASS | 2.3s | 进程正常启动 |
| SM-02 | 主场景加载 | ✅ PASS | 1.8s | 场景根节点存在 |
| SM-03 | 基础渲染 | ✅ PASS | 0.5s | FPS=58 |
| SM-04 | 输入响应 | ⏱️ TIMEOUT | 60s | 输入后无位移变化 |
| SM-05 | 无致命错误 | ✅ PASS | 0.2s | 日志无 ERROR |

## ⚠️ 需关注

- **SM-04 输入响应超时**: 模拟 KEY_RIGHT 后 Player 节点 position.x 无变化，
  可能原因：输入按键未在项目的按键映射中注册，或游戏主循环未处理该输入事件。
```

### 步骤 5：清理测试环境

1. 终止测试启动的所有子进程（HTTP Server、游戏进程）
2. 释放占用的端口
3. 保留报告和截图文件，不删除

## 完成标志

报告已写入 `report_dir` 且测试环境已清理后，输出：

```
<promise>DONE</promise>
```

## 错误处理

| 场景 | 处理方式 |
|------|----------|
| 游戏构建产物不存在 | 停止执行，提示用户先构建/导出 |
| 测试端口被占用 | 自动选择下一个可用端口（范围 18080-18099） |
| 游戏启动后立即崩溃 | 记录 stderr 和 exit code，所有用例标记 BLOCKED |
| 截图功能不可用 | 降级为仅日志模式，报告中标注"截图不可用" |
| 测试进程残留（清理失败） | 记录 PID 到报告的 `cleanup_warnings` 字段，提醒用户手动清理 |
| 报告目录写入失败 | 回退到 stdout 输出 JSON 报告 |

## 与其他 Skill 的协作

```
ai-game-level-generator
    ├─ 生成关卡配置
    └─ 输出 level_id
    ↓
ai-game-asset-pipeline
    ├─ 生成/导入素材
    └─ 写入引擎资源目录
    ↓
开发者 / AI Agent
    ├─ 实现游戏逻辑
    └─ 构建/导出游戏
    ↓
ai-game-playtester (本 Skill)
    ├─ 自动运行游戏
    ├─ 执行测试用例
    ├─ 采集性能数据
    └─ 输出质量报告
    ↓
开发者
    └─ 根据报告修复问题
```

