游戏画面是动态的、UI是自绘的、引擎是多样的------游戏自动化比软件自动化难得多。本文用Airtest+Poco,手把手搭建一套可落地的游戏自动化框架。
目录
- 游戏自动化为什么难
- 游戏自动化技术方案选型
- Airtest+Poco实战
- 图像识别脚本编写
- 控件识别脚本编写
- 搭建游戏自动化框架
- 多设备并行执行
- 自动化接入CI/CD
- 游戏自动化的边界
- 总结
一、游戏自动化为什么难
1.1 游戏自动化的四大难点
难点1:UI是"画"出来的
软件:UI是标准控件(按钮/输入框),有DOM/XML结构
游戏:UI是引擎渲染的,没有标准控件树
→ 传统控件定位方式失效
难点2:画面是动态的
软件:页面相对静态,元素位置固定
游戏:角色在动、特效在飞、镜头在转
→ 图像识别容易误匹配
难点3:引擎多样
Unity / UE / Cocos / 自研引擎
每种引擎的自动化方案都不同
难点4:自动化本身影响性能
软件:自动化几乎不影响被测应用
游戏:截图/识别本身会占资源,影响帧率
→ 可能测出的性能数据失真
1.2 游戏自动化 vs 软件自动化
| 维度 | 软件自动化 | 游戏自动化 |
|---|---|---|
| 定位方式 | 元素定位(ID/XPath) | 图像/控件识别 |
| 稳定性 | 高 | 较低(画面变化大) |
| 开发成本 | 中 | 高 |
| 维护成本 | 中 | 高 |
| 适用场景 | 稳定功能回归 | 固定流程回归 |
| 主流工具 | Selenium/Appium | Airtest/Poco |
二、游戏自动化技术方案选型
2.1 方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 图像识别 | 截图匹配 | 通用性强,任何游戏可用 | 慢、易误匹配 |
| 控件识别(Poco) | 引擎SDK暴露控件树 | 精准、快 | 需接入SDK |
| 内存读取 | 读游戏内存 | 数据精准 | 违法风险、版本敏感 |
| 协议模拟 | 直接发协议包 | 快、稳定 | 需破解协议 |
| 引擎内置 | 引擎自带测试工具 | 深度 | 依赖引擎 |
2.2 推荐组合方案
最佳实践:图像识别 + 控件识别 组合
Airtest(图像识别):
适合:动态画面、无控件树的场景
用途:点击游戏内按钮、验证画面元素
Poco(控件识别):
适合:有控件树的场景(Unity/UE/Cocos)
用途:精准定位UI控件,读取属性
组合使用:
能用控件识别就用控件(快且准)
控件识别不了的用图像识别兜底
三、Airtest+Poco实战
3.1 环境搭建
bash
# 安装Airtest
pip install airtest
# 安装Poco(控件识别)
pip install pocoui
# 下载AirtestIDE(可视化IDE,强烈推荐)
# 官网:https://airtest.netease.com/
3.2 Poco接入游戏
Poco接入流程(以Unity为例):
1. 导入Poco SDK到游戏工程
下载 poco-sdk-unity,导入Unity项目
2. 在游戏场景中挂载Poco脚本
将 PocoManager 预制体拖入场景
3. 游戏打包
打一个带Poco的测试包
4. AirtestIDE连接
连接设备 → 选择Poco辅助窗 → 选择Unity
5. 查看控件树
在IDE中即可看到游戏UI的控件树
3.3 第一个自动化脚本
python
# game_auto.py
"""
游戏自动化测试示例
以登录并进入主界面为例
"""
from airtest.core.api import *
from poco.drivers.unity3d import UnityPoco
import time
# 连接设备
auto_setup(__file__)
# 初始化Poco(Unity引擎)
poco = UnityPoco()
def test_login_and_enter_game():
"""登录并进入游戏主界面"""
# 1. 等待登录界面出现
poco(text="登录").wait_for_appearance(timeout=30)
print("登录界面已加载")
# 2. 输入账号密码
poco("LoginPanel").child("AccountInput").set_text("test_account")
poco("LoginPanel").child("PasswordInput").set_text("test_password")
# 3. 点击登录按钮
poco("LoginPanel").child("LoginBtn").click()
# 4. 等待主界面出现
poco("MainPanel").wait_for_appearance(timeout=30)
print("已进入主界面")
# 5. 验证主界面元素
assert poco("MainPanel").exists(), "主界面未出现"
assert poco("PlayerLevel").exists(), "等级显示缺失"
print("主界面元素验证通过")
if __name__ == '__main__':
test_login_and_enter_game()
四、图像识别脚本编写
4.1 图像识别原理
图像识别流程:
截图 → 与模板图片匹配 → 找到相似度最高的位置 → 返回坐标
关键参数:
threshold:相似度阈值(0-1,默认0.7,越高越严格)
target_pos:点击位置(1-9九宫格,默认5即中心)
rgb:是否用RGB匹配(默认False用灰度)
4.2 图像识别脚本示例
python
# image_auto.py
"""
基于图像识别的游戏自动化
适合无控件树的场景
"""
from airtest.core.api import *
auto_setup(__file__)
def test_ui_by_image():
"""用图像识别测试游戏UI"""
# 1. 等待并点击"开始游戏"按钮
# Template图片需提前截好放在同级目录
touch(Template(r"tpl_start_game.png", threshold=0.8))
sleep(2)
# 2. 等待加载完成
wait(Template(r"tpl_main_ui.png", threshold=0.8), timeout=30)
# 3. 点击"背包"图标
touch(Template(r"tpl_bag_icon.png", threshold=0.8))
sleep(1)
# 4. 验证背包面板出现
assert exists(Template(r"tpl_bag_panel.png", threshold=0.8)), "背包未打开"
# 5. 关闭背包
touch(Template(r"tpl_close_btn.png", threshold=0.8))
def test_combat_flow():
"""测试战斗流程"""
# 进入战斗
touch(Template(r"tpl_battle_entry.png", threshold=0.8))
sleep(3)
# 释放技能(点击技能按钮)
touch(Template(r"tpl_skill_1.png", threshold=0.7))
sleep(1)
touch(Template(r"tpl_skill_2.png", threshold=0.7))
sleep(1)
# 等待战斗结算
wait(Template(r"tpl_battle_result.png", threshold=0.8), timeout=60)
print("战斗流程完成")
if __name__ == '__main__':
test_ui_by_image()
4.3 图像识别提升稳定性技巧
五、控件识别脚本编写
5.1 控件定位方式
python
# Poco控件定位的多种方式
# 1. 按控件名
poco("LoginBtn").click()
# 2. 按文本
poco(text="登录").click()
# 3. 按层级(父子关系)
poco("MainPanel").child("TopBar").child("SettingsBtn").click()
# 4. 按属性
poco("Item").attr("type") # 获取属性
poco(type="Button").click()
# 5. 按索引(同名控件的第几个)
poco("Item")[0].click() # 第一个
poco("Item")[-1].click() # 最后一个
# 6. 组合定位
poco(text="确定", type="Button").click()
5.2 控件操作大全
python
# 基础操作
poco("Btn").click() # 点击
poco("Input").set_text("hello") # 设置文本
poco("Input").get_text() # 获取文本
poco("Text").get_text() # 获取显示文本
# 属性获取
poco("Btn").attr("visible") # 是否可见
poco("Btn").attr("pos") # 位置
poco("Image").attr("sprite") # 图片资源名
# 滑动操作
poco("List").swipe('up') # 向上滑动
poco("List").swipe([0.5, 0.5], [0.5, 0.1]) # 自定义方向
# 等待
poco("Panel").wait_for_appearance(timeout=10) # 等待出现
poco("Panel").wait_for_disappearance(timeout=10) # 等待消失
# 断言
assert poco("Panel").exists() # 存在性
assert poco("Check").attr("visible") # 可见性
5.3 完整业务流程脚本
python
# game_flow_test.py
"""
完整游戏业务流程自动化
登录 → 主界面 → 任务 → 战斗 → 结算
"""
from airtest.core.api import *
from poco.drivers.unity3d import UnityPoco
import allure
poco = UnityPoco()
class GameAutoTest:
"""游戏自动化测试类"""
def setup_method(self):
auto_setup(__file__)
@allure.step("登录游戏")
def login(self, account, password):
poco(text="登录").wait_for_appearance(timeout=30)
poco("AccountInput").set_text(account)
poco("PasswordInput").set_text(password)
poco("LoginBtn").click()
poco("MainPanel").wait_for_appearance(timeout=30)
@allure.step("领取日常任务")
def claim_daily_task(self):
poco("TaskBtn").click()
poco("DailyTab").click()
# 一键领取
if poco("ClaimAllBtn").exists():
poco("ClaimAllBtn").click()
poco("ConfirmBtn").click()
poco("CloseBtn").click()
@allure.step("进入副本战斗")
def enter_battle(self, stage_name):
poco("BattleBtn").click()
poco(text=stage_name).click()
poco("StartBtn").click()
# 等待战斗界面
poco("BattleUI").wait_for_appearance(timeout=30)
@allure.step("自动战斗")
def auto_battle(self):
# 开启自动战斗
poco("AutoBtn").click()
# 等待战斗结束
poco("BattleResult").wait_for_appearance(timeout=180)
@allure.step("验证战斗结果")
def verify_result(self):
assert poco("BattleResult").exists()
result = poco("ResultText").get_text()
assert "胜利" in result or "成功" in result, f"战斗失败: {result}"
def test_full_flow(self):
"""完整流程测试"""
self.login("test_account", "test_password")
self.claim_daily_task()
self.enter_battle("第一章第一关")
self.auto_battle()
self.verify_result()
if __name__ == '__main__':
test = GameAutoTest()
test.setup_method()
test.test_full_flow()
六、搭建游戏自动化框架
6.1 框架目录结构
6.2 游戏基类封装
python
# base/base_game.py
"""游戏自动化基类"""
from airtest.core.api import *
from poco.drivers.unity3d import UnityPoco
import logging
logger = logging.getLogger(__name__)
class BaseGame:
"""游戏基类"""
def __init__(self):
self.poco = UnityPoco()
def click(self, locator):
"""点击控件(支持控件名和文本)"""
if locator.startswith("text="):
self.poco(text=locator[5:]).click()
else:
self.poco(locator).click()
logger.info(f"点击: {locator}")
def wait_element(self, locator, timeout=15):
"""等待元素出现"""
if locator.startswith("text="):
self.poco(text=locator[5:]).wait_for_appearance(timeout)
else:
self.poco(locator).wait_for_appearance(timeout)
def is_exist(self, locator, timeout=3):
"""判断元素是否存在"""
try:
self.wait_element(locator, timeout)
return True
except Exception:
return False
def get_text(self, locator):
"""获取控件文本"""
return self.poco(locator).get_text()
def input_text(self, locator, text):
"""输入文本"""
self.poco(locator).set_text(text)
def screenshot(self, name):
"""截图(用于报告)"""
from airtest.core.api import snapshot
import os
os.makedirs("reports/screenshots", exist_ok=True)
path = f"reports/screenshots/{name}.png"
snapshot(filename=path)
return path
6.3 配置化设备管理
yaml
# config/config.yaml
devices:
- serial: "emulator-5554"
game_package: "com.company.game"
resolution: [1280, 720]
- serial: "emulator-5556"
game_package: "com.company.game"
resolution: [1920, 1080]
accounts:
test_user:
account: "test_account"
password: "test_password"
vip_user:
account: "vip_account"
password: "vip_password"
timeouts:
login: 30
battle: 180
default: 15
python
# utils/driver_util.py
"""多设备管理"""
import yaml
from airtest.core.api import connect_device, set_current
def load_config(path="config/config.yaml"):
with open(path, encoding='utf-8') as f:
return yaml.safe_load(f)
def connect_all_devices():
"""连接配置中的所有设备"""
config = load_config()
devices = []
for dev in config['devices']:
serial = dev['serial']
conn = connect_device(f"Android:///{serial}")
devices.append({
'serial': serial,
'conn': conn,
'config': dev
})
return devices
七、多设备并行执行
7.1 并行执行方案
python
# parallel_runner.py
"""多设备并行执行游戏自动化"""
import threading
from airtest.core.api import connect_device, set_current
from utils.driver_util import load_config
def run_case_on_device(serial, case_func):
"""在指定设备上运行用例"""
conn = connect_device(f"Android:///{serial}")
set_current(serial) # 切换当前设备
try:
case_func()
print(f"[{serial}] 测试通过")
except Exception as e:
print(f"[{serial}] 测试失败: {e}")
def run_parallel(case_func):
"""在所有设备上并行运行"""
config = load_config()
threads = []
for dev in config['devices']:
t = threading.Thread(
target=run_case_on_device,
args=(dev['serial'], case_func)
)
threads.append(t)
t.start()
for t in threads:
t.join()
print("所有设备执行完成")
if __name__ == '__main__':
from cases.test_login import test_login
run_parallel(test_login)
7.2 并行执行的注意事项
注意事项:
1. 账号隔离:每台设备用不同账号,避免互相干扰
2. 数据隔离:确保测试数据不冲突
3. 资源隔离:避免设备间网络/资源竞争
4. 结果汇总:统一收集各设备结果
5. 异常处理:单设备失败不影响其他设备
6. 截图标注:截图按设备区分,方便定位
八、自动化接入CI/CD
8.1 Jenkins Pipeline配置
groovy
// Jenkinsfile
pipeline {
agent any
parameters {
choice(name: 'DEVICE', choices: ['emulator-5554', 'emulator-5556'], description: '测试设备')
string(name: 'BRANCH', defaultValue: 'master', description: '测试包分支')
}
stages {
stage('准备环境') {
steps {
sh 'pip install -r requirements.txt'
// 连接设备
sh 'adb connect ${DEVICE}'
}
}
stage('安装游戏包') {
steps {
sh 'adb -s ${DEVICE} install -r build/game.apk'
}
}
stage('执行自动化') {
steps {
sh '''
pytest cases/ \
--alluredir=reports/allure-results \
-v
'''
}
post {
always {
allure includeProperties: false,
results: [[path: 'reports/allure-results']]
}
}
}
stage('清理环境') {
steps {
sh 'adb -s ${DEVICE} uninstall com.company.game'
}
}
}
}
8.2 触发时机建议
自动化触发时机:
├── 每日凌晨:全量回归
├── 提测时:核心流程冒烟
├── 版本构建后:安装验证
├── 上线前:关键功能回归
└── 定时巡检:稳定性监控
九、游戏自动化的边界
9.1 适合自动化的场景
✅ 适合自动化:
├── 稳定的UI流程(登录、进入游戏)
├── 重复性高的回归测试
├── 多设备兼容性冒烟
├── 长时间稳定性测试(挂机)
├── 性能数据采集
└── 数值批量验证
9.2 不适合自动化的场景
❌ 不适合自动化:
├── 主观体验测试(手感、画面)
├── 探索式测试
├── 一次性的功能测试
├── 频繁变动的UI
├── 需要人工判断的场景
└── 复杂战斗操作(需要人类反应)
9.3 自动化的正确认知
自动化不是万能的:
├── 自动化是"辅助",不是"替代"
├── 自动化适合"重复",不适合"探索"
├── 自动化投入产出比要算清楚
└── 维护成本可能比开发成本更高
正确姿势:
用自动化解决重复劳动
把人力投入到更有价值的探索测试
十、总结
10.1 核心要点
| 要点 | 一句话 |
|---|---|
| 游戏自动化难点 | UI自绘、画面动态、引擎多样 |
| 技术选型 | 图像识别(Airtest) + 控件识别(Poco) |
| 控件识别优先 | 有控件树就用Poco,更准更快 |
| 图像识别兜底 | 无控件树时用图像匹配 |
| 框架搭建 | 基类封装 + 页面对象 + 配置化 |
| 并行执行 | 多设备同时跑,提升效率 |
| 合理定位 | 自动化做回归,人工做探索 |
10.2 游戏自动化学习路径
入门:会用AirtestIDE录制脚本
↓
进阶:会写Poco控件识别脚本
↓
熟练:能搭建自动化框架
↓
高级:多设备并行 + CI集成
↓
专家:能结合性能/数值做综合自动化
10.3 下一篇预告
📌 下一篇:《游戏测试专题第八篇:游戏安全测试与反外挂》------内存修改、协议篡改、自动化脚本,游戏安全测试的攻防实战。
🔥 外挂是游戏的天敌,安全测试是游戏测试里技术门槛最高的方向。下篇见。