UIAutomation的功能测试代码是用Javascript编写的。通过标签和值的访问性来获得UI元素,同时完成相应的交互操作。通过一个实际的测试项目来手把手学习如何使用UIAutomation进行自动化测试。
前期准备:
- 申请 apple id 。
- 将手机的UDID加入到设备列表 (可通过iTools查看UDID)。
- 申请开发者证书 。
- 将iPhone设为开发机(Xcode>Window>Organizer>选设备>use for development)。
- 下载示例应用程序 TestAutomation.xcodeproj (此项目包含2个Tabbar的应用程序)。
- 需要修改 Target>General>Identity>Bundle Identifier改成你申请的证书对应的Identifier。
- 下载Identifier对应的Profiles,并安装。
第一个UIAutomation测试用例
- 通过USB连接手机,打开示例应用程序工程。
- 设置 Target > Build Settings > Code Signing > iOS Developer。
- 启动 Instruments(Xcode > Open Developer Tool >Instrument)。
- 选择 iOS > Automation >Choose。
- 选择 Choose Target > TestAutomation 。
- Scripts > Add > Create 创建新脚本。
- 在脚本编辑器里,输入下面的代码。
var target = UIATarget.localTarget(); var app = target.frontMostApp(); var window = app.mainWindow(); target.logElementTree();
- 运行这段脚本⌘R。运行完成后,可以在日志中停止它。
这样完成了我们的第一个UIAutomation测试用例。
2. 访问和操作用户界面元素
如果一个控件的Accessibility 是可以被访问的,你就可以设置和读取它的值。通过Interface Builder中,设置 Accessibility 为 Enabled。
理解元素的层级结构
在元素层级顶层是UIATarget 类,为了测试你的应用程序必须是前台活跃的程序,代码如下:
<span style="font-size:14px;">UIATarget.localTarget().frontMostApp();</span>
为了获得应用程序主窗口,代码如下:
<span style="font-size:14px;">UIATarget.localTarget().frontMostApp().mainWindow();</span>
通过从0开始的索引来访问文本框,代码如下:
<span style="font-size:14px;">var textField = UIATarget.localTarget().frontMostApp().mainWindow().textFields()[0];
</span>
通过这个元素的名称来访问文本框,代码如下:
<span style="font-size:14px;">var textField = UIATarget.localTarget().frontMostApp().mainWindow().textFields()["User Text"];
</span>
可以在Interface Builder中设置 UIAElement 的name属性,或通过代码方式更改
<span style="font-size:14px;">myTextField.accessibilityEnabled = YES;
myTextField.accessibilityLabel = @"User Text";
</span>
使用logElementTree 方法显示每个元素的所有子元素集。
<span style="font-size:14px;">UIALogger.logStart("Logging element tree ...");
UIATarget.localTarget().logElementTree();
UIALogger.logPass();</span>
该命令的输出被Automation instrument 工具捕获并日志输出,如图:
3. 执行用户界面手势
点击(Tapping)
<span style="font-size:14px;">var tabBar = UIATarget.localTarget().frontMostApp().tabBar();
var tabButton = tabBar.buttons()["First"];
// Tap the tab bar
tabButton.tap();
</span>
可以通过屏幕的坐标,进行点击。代码如下:
<span style="font-size:14px;">UIATarget.localTarget().tap({x:100, y:200});
UIATarget.localTarget().doubleTap({x:100, y:200});
UIATarget.localTarget().twoFingerTap({x:100, y:200});
</span>
缩放(Pinching)需要指定开始和结束坐标,指定手势需要执行的时间,代码如下:
<span style="font-size:14px;">UIATarget.localTarget().pinchOpenFromToForDuration({x:20, y:200},{x:300, y:200},2);
UIATarget.localTarget().pinchCloseFromToForDuration({x:20, y:200}, {x:300, y:200},2);</span>
拖拽 (Dragging)如:滚动一个列表或移动一个元素,代码如下:
<span style="font-size:14px;">UIATarget.localTarget().dragFromToForDuration({x:160, y:200},{x:160,y:400},1);</span>
划动 (Flicking)比较快的拖拽,代码如下:
<span style="font-size:14px;">UIATarget.localTarget().flickFromTo({x:160, y:200},{x:160, y:400});</span>
输入文字
<span style="font-size:14px;">var recipeName = "Unusually Long Name for a Recipe";
UITarget.localTarget().frontMostApp().mainWindow().textFields()[0].setValue(recipeName);</span>
导航标签
<span style="font-size:14px;">var tabBar = UIATarget.localTarget().frontMostApp().mainWindow().tabBar();
var selectedTabName = tabBar.selectedButton().name();
if(selectedTabName != "First"){
tabBar.buttons()["First"].tap();
}</span>
滚动到某个元素位置 可以滚动到某个不知道确切名字的位置
<span style="font-size:14px;">UIATarget.localTarget().frontMostApp().mainWindow().tableViews()[0].scrollToElementWithPredicate("name beginswith ‘ABC’");</span>
添加超时周期 在此周期内它会在失效之前重复的尝试执行指定的动作,动作在未超时之前返回,你的脚本可以处理它。默认周期5秒,可以通过推入一个自定义的超时周期到栈顶。代码如下:
<span style="font-size:14px;">UIATarget.localTarget().pushTimeout(2);
UIATarget.localTarget().popTimeout();</span>
建议不使用显式的迟延,某些场景必须使用时如下:
<span style="font-size:14px;">UIATarget.localTarget().delay(2);</span>
访问键盘上的按钮
<span style="font-size:14px;">app.keyboard().buttons()["Return"].tap();</span>
输出测试结果
<span style="font-size:14px;">var testName = "Module 001 Test";
UIALogger.logStart(testName);
//some test code
UIALogger.logPass(testName);</span>
增加截屏及日志输出
<span style="font-size:14px;">var testName = "Module 001 Test";
UIALogger.logStart(testName);
//some test code
UIALogger.logMessage("Starting Module 001 branch 2, validating input.");
//capture a screenshot with a specified name
UIATarget.localTarget().captureScreenWithName("SS001-2_AddedIngredient");
//more test code
UIALogger.logPass(testName);</span>
验证测试结果
<span style="font-size:14px;">if (cell.isValid()) {
UIALogger.logPass(testName);
}
else {
UIALogger.logFail(testName);
}</span>
处理非预期的提示框,在写自动化测试过程中,处理提示框是很难的一件事情:你已经很认真的写好了你的测试用例,然后在你准备睡觉之前将它跑起来,然后,到第二天早上,你发现你的测试用例被一个未知消息提示框给毁了。
<span style="font-size:14px;">UIATarget.onAlert = function onAlert(alert) {
var title = alert.name();
UIALogger.logWarning("Alert with title '" + title + "' encountered.");
// return false to use the default handler
return false;
}</span>
处理预期的提示框
<span style="font-size:14px;">UIATarget.onAlert = function onAlert(alert) {
var title = alert.name();
UIALogger.logWarning("Alert with title '" + title + "' encountered.");
if (title == "The Alert We Expected") {
alert.buttons()["Continue"].tap();
return true; //alert handled, so bypass the default handler
}
// return false to use the default handler
return false;
}</span>
旋转屏幕方向
<span style="font-size:14px;">var target = UIATarget.localTarget();
var app = target.frontMostApp();
//set orientation to landscape left
target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_LANDSCAPELEFT);
UIALogger.logMessage("Current orientation now " + app.interfaceOrientation());
//reset orientation to portrait
target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT);
UIALogger.logMessage("Current orientation now " + app.interfaceOrientation());</span>
多任务场景 当要测试一些多应用交互的场景时,需要将程序切到后台,等待一段时间后再返回活动状态,通过以下代码,可以模拟用户点击Home按钮。
<span style="font-size:14px;">UIATarget.localTarget().deactivateAppForDuration(10);</span>
完整测试用例脚本
1. 在Scripts窗口里, 移除当前的脚本
2. 点击“Add > Import”然后选择TestAutomation/TestUI/Test-1.js(将下面的代码保存到这个路径)
3. 点击录制按钮 (⌘R)
下面是Test-1.js代码:
<span style="font-size:14px;">var testName = "Test 1";
var target = UIATarget.localTarget();
var app = target.frontMostApp();
var window = app.mainWindow();
UIALogger.logStart( testName );
app.logElementTree();
//-- select the elements
UIALogger.logMessage( "Select the first tab" );
var tabBar = app.tabBar();
var selectedTabName = tabBar.selectedButton().name();
if (selectedTabName != "First") {
tabBar.buttons()["First"].tap();
}
//-- tap on the text fiels
UIALogger.logMessage( "Tap on the text field now" );
var recipeName = "Unusually Long Name for a Recipe";
window.textFields()[0].setValue(recipeName);
target.delay( 2 );
//-- tap on the text fiels
UIALogger.logMessage( "Dismiss the keyboard" );
app.logElementTree();
app.keyboard().buttons()["Return"].tap();
target.deactivateAppForDuration(5);
target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_LANDSCAPELEFT);
UIALogger.logMessage("Current orientation is " + app.interfaceOrientation());
target.delay(1);
target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT);
UIALogger.logMessage("Current orientation is " + app.interfaceOrientation());
target.delay(1);
var textValue = window.staticTexts()["RecipeName"].value();
if (textValue === recipeName){
UIALogger.logPass( testName );
}
else{
UIALogger.logFail( testName );
}</span>
这段脚本先启动待测程序,然后,如果第一个tab没有被选的话就切换到第一个tab,并将上面的文本框的值设成“Unusually Long Name for a Recipe”,接着收起虚拟键盘。UIALogger的logMessage( String message) 方法用来将你想打印的信息输出到日志上去,UIALogger的logPass(String message)方法指明你的测试脚本已经成功的完成测试了。