演练:创建消消乐游戏的图像自动化项目
这里有一款Qt自带的桌面小游戏样例,今天的任务是完成这个小游戏的自动化。游戏类型的应用自动化比较困难的地方就在于这些应用往往没有控件的概念,没有应用中常用的标准控件。所以我们今天用图像自动化的方式来完成它的自动化。
CukeTest提供的图像自动化能力,是通过图案对象Pattern
对其执行操作。在运行自动化时,会自动在当前屏幕中匹配与图案对象相似的区域,并执行相应的操作,如点击、悬停、拖拽等。由于某些应用界面不使用标准控件,无法通过直接识别控件对象的方式完成自动化,比如游戏,或者使用了自绘制的UI框架,这种就可以借助图像的方式来完成自动化了。
介绍
这个游戏的规则比较简单,只要点击连在一起的同色色块就可以消除并拿到分数,通常来说一次性消除的色块越多,额外奖励的分数越多。但现在我们只需要把所有能消除的色块都消除掉,先不考虑拿高分的情况。
因为游戏的目标是把所有连在一起的色块消除掉,游戏里只有横着连和竖着连两种情况,加上一共三种颜色的色块,所以只需要识别六种图案对象即可,工作量并不大。
本次演练使用Windows平台介绍。同样的操作步骤也可在Linux版的CukeTest实现。
开始编写自动化
打开被测应用
本次被测应用是使用Qt QML技术开发的应用,因此建议系统环境中安装Qt后在其Example
目录中找到被测应用,默认路径为C:\Qt\4.8.6\demos\declarative\samegame\release\samegame.exe
。双击该exe
文件就可以看到应用启动了:
识别对象
使用CukeTest自带的Windows模版创建项目。然后点击项目中的.tmodel
文件打开模型管理器,接下来将在模型管理器中添加被测应用中要操作的控件对象。
前面也提过,这类应用不适合使用对象识别方式,但是有个例外——应用的窗口仍然可以作为控件对象识别。识别窗口对象会给接下来的图像识别带来一些好处,因为CukeTest支持在Windows控件对象下创建图案对象,称作级联图案对象,能够实现在特定控件中匹配与图案对象相似的区域。这能够带来以下几点好处:
- 匹配更加精确,因为不会匹配到控件外的区域,因此减少了错误匹配的情况;
- 匹配更加快速,因为默认会从整个屏幕中通过算法匹配图案,如果屏幕很多或是分辨率较高,就会出现搜索区域大,匹配速度变慢的情况,如果把匹配区域限制为某控件区域,就能大大减少需要匹配的区域从而提高匹配速度了。
第一步:侦测应用的窗口
第一步应该识别应用窗口,因为窗口通常可以使用对象识别直接侦测到,所以点击模型管理器中的【识别视窗对象】按钮,侦测应用窗口,添加到树中,得到:
第二步:创建级联的图案对象
为了开始一局新游戏,需要点击窗口左下角的【new game】按钮,很快就能发现这个按钮其实不是一个Qt Widget控件,无法使用对象识别,因此需要将其创建为图案对象,为了识别更准确,我们将其创建为应用窗口下的级联对象。
点击上一步中识别的Window对象,在右侧属性面板中的【控件截屏】标签页中点击【创建子图案】,在截屏中选择左下角的【new game】按钮,得到新的图案对象:
如果发现窗口的截图中不包含要添加的内容,可以点击该控件并再次截图更新。
第三步:创建色块图案
接着是最重要的一步,创建用于完成游戏目标的图案——三种颜色的相连色块,分为横竖相连两种,一共需要识别六种图案对象。首先点击【new game】按钮开始一局游戏。接着点击工具栏的【添加图案对象】,在应用中分别选取:横连的红色色块、纵联的红色色块、横连的绿色色块、纵联的绿色色块、横连的蓝色色块、纵联的蓝色色块共6个图像对象,这些对象可以改名为合适的名称。
6个图像对象的属性分别为:
这里并没有将这几个对象通过级联的方式创建是为了演示不同的方式添加图案对象。
第四步:调节图案匹配度
添加完这些对象你或许会思考,在这个应用中一个图案对象可以匹配到多个结果,如何保证刚才添加的图案对象可以准确地匹配到所有结果?选中模型树中的图案对象,在右侧属性面板中的【标识属性】标签页中点击【调整图像参数】按钮,就会标识出所有的匹配结果,同时可以调整 相似度阈值 来调整匹配的精度,默认为0.98
,但在这个应用中设置为0.95
更合适。
调节到0.95
后可以看到所以的目标都被匹配到了:
每个图案对象的相似度阈值属性是独立的,所以需要给六个图案对象分别修改。
(可选)第五步:识别游戏胜利提示
最后可以选择识别完成游戏后出现的胜利提示,以此判断游戏是否已经结束。
至此完成了所有识别,完成之后应该得到这样一棵模型树:
到这一步已经完成了这个项目的80%了,让我们接着继续。
编写剧本
回想我们刚刚试玩游戏的操作流程:
- 打开被测应用
- 开始一局新游戏
- 消除所有相连的同色色块
- 消除红色
- 消除绿色
- 消除蓝色
- 重复3.1到3.3步
- 验证没有更多色块可以消除
- (可选)验证胜利提示弹出
转换为剧本如下:
# language: zh-CN
功能: 自动玩游戏
通过qt自带的消消乐样例进行图像自动化开发测试
场景: 完成一局游戏
假如启动qml应用
那么开始新游戏
当消除"红"色
当消除"蓝"色
当消除"绿"色
当消除"红"色
当消除"蓝"色
当消除"绿"色
当消除"红"色
当消除"蓝"色
当消除"绿"色
那么 验证没有更多可以消除的色块
那么验证应该看到胜利提示
这里简单的将消除三种色块的操作重复了三次,绝大多数情况都够消除干净色块了。
编写脚本
编写脚本前
在开始动手写脚本之前,需要做几个准备操作:
- 从剧本界面中可以直接点击步骤旁的灰色箭头生成脚本模版;
- 将项目中的
.tmodel
模型文件拖拽到编辑器中,生成两行引用模型文件的脚本。JavaScriptconst { WinAuto } = require('leanpro.win'); let model = WinAuto.loadModel(__dirname + "/model1.tmodel");
步骤:启动qml应用
这一步要打开被测应用,CukeTest提供的启动应用的脚本可以从工具箱的“常用”一栏中拖拽launchProcess()
方法到脚本编辑器中,启动路径就使用刚刚启动被测应用的路径C:\Qt\4.8.6\demos\declarative\samegame\release\samegame.exe
。
为了保证应用启动后才继续执行接下来的自动化操作,加入了后面的脚本,借助模型中的Window
控件来判断应用窗口是否已打开。
Given("启动qml应用", async function () {
Util.launchProcess(appPath);
if (!await model.getWindow("Window").exists(5)) {
throw "Testing application did not lauch in-time.";
} // 等待应用启动
});
步骤:开始新游戏
这一步只要在被测应用启动后,点击应用左下角的按钮开始新游戏即可。
Given("开始新游戏", async function () {
await model.getPattern("new_game").click();
await Util.delay(500);
});
第二行中加入了延时,是为了等待游戏动画效果结束,由于图像识别的操作是在执行开始时对当前屏幕进行一次截图,接着在截图中匹配,所以应该保证在内容加载完毕后再开始图像自动化的操作。后面在点击色块消除后也需要等待动画效果结束。
步骤:消除{string}色
这个步骤定义函数会接受不同的参数,如红、蓝、黄,然后用它来获得不同的Pattern
对象执行操作。Pattern对象提供了定位方法locate()
,能够定位当前屏幕中的匹配区域,如果没有则会抛错,我们利用这个机制来循环的消除屏幕中满足条件的色块。
const colorMap = {
'红': 'red',
'绿': 'green',
'蓝': 'blue'
}
When("消除{string}色", async function (color) {
let target;
while(1) {
try {
target = await model.getPattern(`${colorMap[color]}_row`).locate();
await target.click();
await Util.delay(500);
} catch (err) {
console.log(`没有新的${color}横`);
break;
}
}
while (1) {
try {
target = await model.getPattern(`${colorMap[color]}_col`).locate();
await target.click();
await Util.delay(500);
} catch (err) {
console.log(`没有新的${color}竖`);
break;
}
}
let screenshot = await model.getWindow("Window").takeScreenshot();
this.attach(screenshot, 'image/png');
});
注意:
可以注意到在每次循环结束前都会进行一个500毫秒的延时等待,这是因为每次消除都要播放动画效果,需要等待动画结束、新的排列确定后再进行下一次匹配消除。
也可以调用
Pattern
对象的findAll()
方法获取所有的匹配结果组成的数组,如果数组长度为0则退出循环。
这个步骤函数只能消除某一种颜色,应用中共有三种颜色需要消除,因此要用三个步骤分别消除红蓝绿三种颜色的色块。但问题又出现了,消除完蓝色色块可能会因为排列变动产生新的红色色块,所以这里先简单的将三个步骤重复三次,绝大多数情况下都够用了。
最后两行的作用是将当前步骤执行完毕后的应用情况截个图贴到最后生成的报告中,以便于在运行结束后检查各个步骤运行结束后的应用情况。
利用对象(键值对)提高可读性
可以注意到脚本中我们定义了一个对象(在其它语言中也称作键值对或字典),通过这个对象可以把步骤描述中操作红色映射为模型中的Pattern
对象 red_col
与 red_row
,而不需要在步骤描述中直接使用变量名。如果定义这个对象,那么步骤描述需要从:
当消除"红"色
变为:
当消除"red"色
很明显对于中文语境,前者的描述更加友好。
步骤:验证游戏完成
当完成所有的消除后,我们需要添加检验点判断游戏是否完成,那么可以从两方面进行检验:
- 应用中没有更多可以被消除的色块;
- 是否弹出游戏胜利的提示
检验点使用的是assert
断言库,可以从工具箱的“常用”中拖拽“断言”模块到脚本编辑器中生成引用。这里将两种检验点都写出来:
Then("验证没有更多可以消除的色块", async function () {
let redColCount = await model.getPattern(`red_col`).findAll();
assert.strictEqual(redColCount.length, 0);
let redRowCount = await model.getPattern(`red_row`).findAll();
assert.strictEqual(redRowCount.length, 0);
let greenColCount = await model.getPattern(`green_col`).findAll();
assert.strictEqual(greenColCount.length, 0);
let greenRowCount = await model.getPattern(`green_row`).findAll();
assert.strictEqual(greenRowCount.length, 0);
let blueColCount = await model.getPattern(`blue_col`).findAll();
assert.strictEqual(blueColCount.length, 0);
let blueRowCount = await model.getPattern(`blue_row`).findAll();
assert.strictEqual(blueRowCount.length, 0);
let screenshot = await model.getWindow("Window").takeScreenshot();
this.attach(screenshot, 'image/png');
});
Then("验证应该看到胜利提示", async function () {
if (!await model.getPattern("enter_name").exists(5)) {
throw "没有胜利提示出现"
}
let screenshot = await model.getWindow("Window").takeScreenshot();
this.attach(screenshot, 'image/png');
});
在这两步中也同样添加了截图和贴到结果报告中的操作,方便进行进一步的人工检验。
总结
以上就是使用CukeTest提供的图案对象Pattern
完成一款游戏应用自动化的全部流程,不仅可以单独使用,结合对象识别的能力,能够处理更多的桌面自动化场景,从而让自动化更加的顺畅。本次演练的代码也可以在CukeTest安装包中的样例中找到,搜索"游戏消消乐",被测应用可通过安装Qt获得。