iOS UI自动化测试详解
前言:
小目标
关于UI自动化的定义,我想要的是自动地按照流程去点击页面、输入数据,不需要人去参与,节省人工时间。比如登录,能够自己去填写用户名&密码,然后点击按钮跳转到下一个页面等。在能够保证业务的足够稳定性的条件下,UI自动化测试能够节省很多回归功能的人力。这就是我的一个小目标。
测试需要全面,需要对结果去做判断。如果熟悉单元测试或接口自动化的朋友,应该知道这些其实就是功能覆盖、用例设计、断言……这些概念。于是小目标就升级成了,一个可以重复执行,多场景的,结果可预测的UI自动化测试。
正文:
测试基础
软件测试是为了发现错误而执行程序的过程。或者说,是根据软件开发各阶段的规格说明和程序内部结构而精心设计的一批测试用例(即输入数据及预期的输出结果),并利用这些测试用例去运行程序,以发现程序错误的过程。
白盒测试(White Box Testing) 又称结构测试、逻辑驱动测试,完全了解程序的结构和处理过程,按照程序的内部逻辑测试程序,检验程序中的每条通路是否都能按预定要求准确工作;对应到实际工作中,代码diff就是白盒测试。
黑盒测试(Black Box Testing) 又称功能测试,在程序接口进行的测试,已知产品的功能设计规格,可以进行测试证明每个实现了的功能是否符合要求。对应到工作中,Checklist&Case就是功能测试。UI自动化测试,是将功能测试中大量的重复性操作(例如线上回归)提取出来,用脚本的形式驱动设备进行测试,有着可重复运行,执行效率高的特点。
测试用例
测试用例是为特定的目的而设计的一组测试输入、 执行条件和预期的结果。
1.测试用例需要能够覆盖被测试的功能。
2.测试的结果的正确性是可判定的,每一个测试用例都应该有相应的期望结果。
3.即对同样的测试用例,系统的执行结果应当是相同的。
如何覆盖被测试功能,我们需要对输入进行分类划分:等价类划分方法,边界值分析方法。
等价类划分方法,将程序的输入域划分为若干个等价类(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。可以有效减少输入类型。例如用户名输入,可以划分为允许的字符类型:数字、字母,不允许的字符类型:特殊字符,汉字,空格等。
边界值分析方法,边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。例如用户名长度是4-10位,我们依据边界值,就需要对3、4、10、11这四个边界值分别做测试。
相对的,输出类划分后,对每一种输入都有唯一对应的输出,这个输出和功能无关,不管是正常流还是异常流,都需给出唯一结果。我们称之为断言,断言一般分为这这几类:等于、不等于、包括、不包括 或复杂的组合逻辑。
UI自动化测试用例
UI自动化在用例设计上也需满足上述性质,此外在输入中需要过程中需要定义UI的操作,同时在结果的判定上,会依赖UI元素的展示和页面的状态。比如某种异常状态下会弹窗提示,我们则需要去获取这个弹窗,然后对弹窗上UI元素进行比对和判断,而无法去做返回码的比较判断。关于页面状态的断言,相对于直接数据的比较判断不够直接、难度较大,当前还有很多难点。
2.UIAutomation
iOS4时代,Apple发布了一个名为UIAutomation的测试框架,它可以用来在真实设备和iPhone模拟器上执行自动化测试。UIAutomation的功能测试代码是用Javascript编写的。UIAutomation和Accessibility有着直接的关系,你将用到通过标签和值的访问性来获得UI元素,同时完成相应的交互操作。
UIAutomation的js测试脚本,需要在Instrument的Automation控件上测试。但是在Xcode8.0之后,Instrument已经不再有这个模块了。而Apple也不再对它维护了,推广使用UITest来替代它。
Accessibility
UIAutomation的实现原理依赖于UI控件的Accessibility,这项技术是为了辅助身体不便的人士使用 app。VoiceOver 是 Apple 的屏幕阅读技术,UI Accessibility 的基本原则就是对屏幕上的 UI 元素进行分类和标记,两者配合,通过阅读或者聆听这些元素,用户就可以在不接触屏幕的情况下通过声音来使用 app。
如果一个控件的Accessibility是可以被访问的,你就可以设置和读取它的值,作相关的操作,而当一个控件的Accessibility不可见时,你就没有办法通过automation访问它。每一个可以被访问(Accessibility enable)的UIKit控件都可以用一个Javascript对象来描述,它就是一个UIAElement。UIAElement有几个属性:name, value, elements, parent。UIKit层次树结构,可以直接映射到UIAElement的层次树。有了基本属性和层次树结构,我们就可以方便的使用JS对App中的空间进行操作了。
看这个简单的小栗子,操作流程:找到tabbar->找到名称是“First”的button点击->给第一个textField设值->点击键盘的return->取id是“RecipeName”的staticTexts(label)的值->断言该label内容和之前输入内容的相等。
可以看出寻找控件的两种方式:依据控件Accessibility identitifer查找;依据控件在数组的下标查找。前者需要你去设置,增加开发时成本。后者的变化性高,UI层级改变导致你的用例改变。
test("Test 1", function(target, app) {target.logDevice();var window = app.mainWindow();app.logElementTree();//-- select the elementsUIALogger.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 fielsUIALogger.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 fielsUIALogger.logMessage( "Dismiss the keyboard" );app.logElementTree();app.keyboard().buttons()["return"].tap();var textValue = window.staticTexts()["RecipeName"].value();assertEquals(recipeName, textValue);
});
3.UITest
UITest是Apple随着Xcode 7和iOS 9新的UI自动化测试框架。较之前不同的是,在UITest框架下,我们可以使用自己熟悉的Objc和Swift语言来编写自动化测试的脚本。除此之外,UITest最大的亮点是支持屏幕录制——通过对App的操作自动生成相应的测试脚本代码。这意味着非专业人士,也可以很方便地参与到自动化测试里来。
原理和UIAutomation一样,UITest也依赖控件的Accessibility属性,UITest为所有UIKit控件提供了一个XCUI开头的代理类。比如UIApplication,对应的是XCUIApplication,在 UI Testing 中代表整个 app 的对象。
对于一般的UIKit对象,Apple提供XCUIElement对象作为映射。我们不能直接通过得到的 XCUIElement 来直接访问被测 app 中的元素,而只能通过 Accessibility 中的像是 identifier 或者 frame 这样的属性来获取 UI 的信息。关于具体的可用属性,可以参看 XCUIElementAttributes 的文档。虽然不能直接通过Element得到属性,但是可以通过- descendantsMatchingType:,访问子节点,从而得到层级结构。
hello world和屏幕录制操作,可以参考喵大的笔记,附有demo哦。
4.Appium
UITest支持iOS开发熟悉的编程语言、屏幕录制等多项强大功能,但这些都是和Xcode这个IDE绑定的。当我们在Xcode里写完代码,本地运行,这种种更像是单元测试,而没有太多自动化的味道了。一段真正的自动化测试脚本,应该和被测代码分开,能够部署到一台服务器上自动运行,让一项自动化测试真正自动化起来,成为了我的小目标3.0版本。在这里,Appium这个强大的自动化测试工具登场了,看看他的自我介绍。
跨平台、开源、多设备多系统的支持,而且文档详细,还有中文版本,实在是业界良心。
背景
关于Appium的介绍,官方的文档已经说得很清楚了,我这儿大致提炼一下几个重要的点。
1.无需为了自动化,而重新编译或者修改你的应用
在iOS上,这个特性是显然的。前面也说道了,因为Apple提供了Accessibility特性,让你可以得到控件响应的id,frame等。还可以通过代理,从而得到树形层级结构。
2.跨平台
这主要归功于Appium的自动化统一接口WebDriver API.WebDriver(也就是 “Selenium WebDriver”)。我们使用这套统一的接口,在各自平台上去解释,得到不同的底层实现。比如iOS这边是UIAutomation和UITest,Android则是UiAutomator和Instrumentation。
3.多种语言编写&执行测试脚本
这得益于Appium的C/S架构,我们将编写执行脚本的部分称为客户端(Clinet),脚本内容遵循统一接口。每一行,其实就是Client向服务器去发送一条Http消息,然后Server解析并翻译成对应平台(iOS/Adr)的实际测试命令,再发送给Device,再执行。可以通过下面的示意图简单了解。
因为Server层的存在,和统一接口的隔离,实现了脚本实现和执行条件的多样性。现已知的就支持Java、Python、js、Ruby等方式的Client API。
安装指南
同样,官方文档的快速开始也给出了详尽的步骤。但是实际上我在MAC上安装Appium的时候,还是遇到了各种各样的坑。这个指南部分,希望能帮助大家趟过这些坑。
0.下载图形化桌面应用 Appium App
目前更新到1.5,不支持Xcode8 和UITest。如果还在使用Xcode 7系列的版本可以直接下载使用。
1.安装 homebrew & node 如果接触过web前端的同学应该不会陌生。
2.node安装appium
npm install -g appium
到这里需要挂代理翻墙。如果没有条件的话,可以使用淘宝的NPM 镜像。
安装到一半,应该会遇到/usr/local目录权限问题。这里给个小提示,建议给/usr/local/lib/node_modules/appium开全权限sudo chmod -R 777 ./ ,不建议使用sudo,据说会遇到别的问题。如果中途遇到错误,使用
npm uninstall -g appium
命令卸载,然后重装。如果卸载不了,就直接去Finder/usr/local/lib/node_modules/appium目录删除就好了。
安装好之后,运行
$ appium &
命令就可以启动Appium的server了。
Hello World!
安装好了Appium server之后,可以开始运行第一个程序了。去官网下载示例代码。下载之后你能看到有一个apps的文件夹和examples文件夹,前者放的是示例程序,后面是不同语言客户端的测试脚本。打开 apps/TestApp,运行能看到这是一个单页面的程序,最上方有两个输入框,输入数字,点击Compute Sum按钮,就能得出两者相加的和。
下面我们就来做这个部分的UI自动化测试。找一个自己熟悉的Client语言去执行自动化脚本,比如Java。打开,是一个Maven工程,下载了相应依赖库。之后打开SimpleTest文件,因为Xcode 8不支持UIAutomation框架,所以需要在setup中指定capability方式为XCUItest。
capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "XCUITest");
capabilities.setCapability("platformVersion", "10.0");
capabilities.setCapability("deviceName", "iPhone 6");
运行testUIComputation方法。可以看到模拟器启动了,但是之后会遇到Carthage找不到的问题
装上之后
官方Issues中找到了答案,WebDriverAgent缺少webpack。使用npm i -g webpack安装就好。PS:和之前一样使用淘宝源或者挂一个代理。
完成了以上步骤之后,你就能看到你的Test在模拟器中自动运行,随着Terminal大量的log输出,测试结果也成功返回到Java的Client,在IDEA中看到执行结果,PASS!到此为止,第一个自动化测试被执行成功了~
Client - Server - Device交互
driver.findElements
我们以java代码driver.findElements(By.className(“UIATextField”))找到textField数组的过程为例子,去分析一下log,看看一次交互式怎么进行的。
// client ----http----> server
[HTTP] --> POST /wd/hub/session/94f7526c-94ba-4ece-8740-d94bd3d4f50f/elements {"using":"class name","value":"UIATextField"}[MJSONWP] Calling AppiumDriver.findElements() with args: ["class name","UIATextField","94f7526c-94ba-4ece-8740-d94bd3d4f50f"]
[debug] [XCUITest] Executing command 'findElements'
[debug] [BaseDriver] Valid locator strategies for this request: xpath, id, name, class name, -ios predicate string, accessibility id
[XCUITest] Rewrote incoming selector from 'UIATextField' to 'XCUIElementTypeTextField' to match XCUI type. You should consider updating your tests to use the new selectors directly
[debug] [BaseDriver] Waiting up to 0 ms for condition// server ----http----> device
[JSONWP Proxy] Proxying [POST /elements] to [POST http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/elements] with body: {"using":"class name","value":"XCUIElementTypeTextField"}// device ----http----> server
[JSONWP Proxy] Got response with status 200: {"value":[{"ELEMENT":"1A29AE60-5433-4B52-83F8-B4E2C794972E","type":"XCUIElementTypeTextField","label":"TextField1"},{"ELEMENT":"575EE2C2-4AFF-4320-B4D0-C30F59410DA9","type":"XCUIElementTypeTextField","label":"TextField2"}],"sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708","status":0}[MJSONWP] Responding to client with driver.findElements() result: [{"ELEMENT":"1A29AE60-5433-4B52-83F8-B4E2C794972E","type":"XCUIElementTypeTextField","label":"TextField1"},{"ELEMENT":"575EE2C2-4AFF-4320-B4D0-C30F59410DA9","type":"XCUIElementTypeTextField","label":"TextField2"}]
// server ----http----> client
[HTTP] <-- POST /wd/hub/session/94f7526c-94ba-4ece-8740-d94bd3d4f50f/elements 200 225 ms - 285
其中
[MJSONWP] 打印的是S内部的处理日志。
[JSONWP Proxy] 打印的是S/D之间的通信日志。
可以看到,Client、Server、Device是通过Http协议通信的,大致流程为:
Client通过http协议,将指定格式的命令发送给server。server调用中间层AppiumDriver解析命令,发送给实际的处理者。实际处理者依据平台、版本不同而不一样,这里是XCUITest(iOS)。而Device(iphone、模拟器)和Server的通讯也是通过http协议。
到这里,大家会有疑问了,Server怎么找到对应的Device,他们之间如何通过Http通讯的?这里就要引出WebDriverAgent。它的作用:
WDA的inspector演示:直接在浏览器端打开 http://192.168.0.105:8100/inspector
TextField赋值
再看TextField赋值java代码elem.sendKeys(String.valueOf(rndNum));的执行步骤
[HTTP] --> POST /wd/hub/session/94f7526c-94ba-4ece-8740-d94bd3d4f50f/element/1A29AE60-5433-4B52-83F8-B4E2C794972E/value {"id":"1A29AE60-5433-4B52-83F8-B4E2C794972E","value":["1"]}[MJSONWP] Calling AppiumDriver.setValue() with args: [["1"],"1A29AE60-5433-4B52-83F8-B4E2C794972E","94f7526c-94ba-4ece-8740-d94bd3d4f50f"]
[debug] [XCUITest] Executing command 'setValue'[JSONWP Proxy] Proxying [GET /element/1A29AE60-5433-4B52-83F8-B4E2C794972E/attribute/type] to [GET http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/element/1A29AE60-5433-4B52-83F8-B4E2C794972E/attribute/type] with no body
[JSONWP Proxy] Got response with status 200: "{\n \"value\" : \"XCUIElementTypeTextField\",\n \"sessionId\" : \"79CBBB84-DB6E-48BA-B79F-91539E1E4708\",\n \"status\" : 0\n}"
[debug] [BaseDriver] Set implicit wait to 0ms
[debug] [BaseDriver] Waiting up to 0 ms for condition
[JSONWP Proxy] Proxying [POST /element] to [POST http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/element] with body: {"using":"class name","value":"XCUIElementTypeKeyboard"}
[JSONWP Proxy] Got response with status 200: {"value":{"using":"class name","value":"XCUIElementTypeKeyboard","description":"unable to find an element"},"sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708","status":7}
[debug] [XCUITest] No keyboard found. Clicking element to open it.
[JSONWP Proxy] Proxying [POST /element/1A29AE60-5433-4B52-83F8-B4E2C794972E/click] to [POST http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/element/1A29AE60-5433-4B52-83F8-B4E2C794972E/click] with body: {}
[JSONWP Proxy] Got response with status 200: {"status":0,"id":"1A29AE60-5433-4B52-83F8-B4E2C794972E","value":"","sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708"}
[debug] [BaseDriver] Waiting up to 0 ms for condition
[JSONWP Proxy] Proxying [POST /element] to [POST http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/element] with body: {"using":"class name","value":"XCUIElementTypeKeyboard"}
[JSONWP Proxy] Got response with status 200: {"value":{"ELEMENT":"FB515A63-3249-419E-8106-2681A1FEBA24","type":"XCUIElementTypeKeyboard","label":null},"sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708","status":0}
[debug] [BaseDriver] Set implicit wait to 0ms
[JSONWP Proxy] Proxying [POST /element/1A29AE60-5433-4B52-83F8-B4E2C794972E/value] to [POST http://localhost:8100/session/79CBBB84-DB6E-48BA-B79F-91539E1E4708/element/1A29AE60-5433-4B52-83F8-B4E2C794972E/value] with body: {"value":["1"]}
[JSONWP Proxy] Got response with status 200: {"status":0,"id":"1A29AE60-5433-4B52-83F8-B4E2C794972E","value":"","sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708"}[MJSONWP] Responding to client with driver.setValue() result: null
[HTTP] <-- POST /wd/hub/session/94f7526c-94ba-4ece-8740-d94bd3d4f50f/element/1A29AE60-5433-4B52-83F8-B4E2C794972E/value 200 2247 ms - 76
这看出setValue命令,包含几个步骤:检查是否打开键盘、获取输入框、弹出键盘、输入框赋值,每一个步骤都会和Device通讯。
发送数据
session/94f7526c-94ba-4ece-8740-d94bd3d4f50f 这个指定了当前这个case的唯一id,在第一次通信时确立。由于第一次通信比较复杂,在最后会讲到。之后是id为1A29AE60-5433-4B52-83F8-B4E2C794972E 的Element,刚好是上一次请求列表的第一个元素,表明这次是对这个元素做操作。再看value,是一个json,定义的刚好是这个Eelemet和它的值,这些就是自动化统一接口的格式,一个简单命令的定义。
建立Session
[Appium] Appium REST http interface listener started on 0.0.0.0:4723
[HTTP] --> POST /wd/hub/session {"desiredCapabilities":{"app":"/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app","automationName":"XCUITest","platformName":"iOS","deviceName":"iPhone 6","platformVersion":"10.0"}}
[MJSONWP] Calling AppiumDriver.createSession() with args: [{"app":"/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app","automationName":"XCUITest","platformName":"iOS","deviceName":"iPhone 6","platformVersion":"...[Appium] Creating new XCUITestDriver session
[Appium] Capabilities:
[Appium] app: '/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app'
[Appium] automationName: 'XCUITest'
[Appium] platformName: 'iOS'
[Appium] deviceName: 'iPhone 6'
[Appium] platformVersion: '10.0'[debug] [XCUITest] XCUITestDriver version: 2.0.33
[BaseDriver] Session created with session id: 94f7526c-94ba-4ece-8740-d94bd3d4f50f
[debug] [XCUITest] Xcode version set to '8.0'
[debug] [XCUITest] iOS SDK Version set to '10.0'
[iOSSim] Constructing iOS simulator for Xcode version 8.0 with udid 'B9747B0B-C664-4C44-A651-9C3D8F7A980A'// 开启模拟器
[XCUITest] Determining device to run tests on: udid: 'B9747B0B-C664-4C44-A651-9C3D8F7A980A', real device: false
[BaseDriver] Using local app '/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app'
[debug] [XCUITest] Checking whether app '/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app' is actually present
[debug] [XCUITest] App is present
[debug] [ios-app-utils] Getting bundle ID from app '/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app': 'io.appium.TestApp'
[debug] [iOSLog] Starting iOS 10.0 simulator log capture
[debug] [iOSLog] System log path: /Users/zhiyu.zhao/Library/Logs/CoreSimulator/B9747B0B-C664-4C44-A651-9C3D8F7A980A/system.log
[XCUITest] Setting up simulator
[debug] [iOS] No reason to set locale
[debug] [iOS] No iOS / app preferences to set
[XCUITest] Simulator with udid 'B9747B0B-C664-4C44-A651-9C3D8F7A980A' not booted. Booting up now
[debug] [iOSSim] Killing all iOS Simulators
zhiyu.zhao@999999999:~|⇒ [iOSSim] Starting simulator with command: open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app --args -CurrentDeviceUDID B9747B0B-C664-4C44-A651-9C3D8F7A980A
[iOSSim] Tailing simulator logs until we encounter the string "SMS Plugin initialized"
[iOSSim] We will time out after 60000ms
[debug] [iOSSim] Waiting an extra 10000ms for the simulator to really finish booting
[debug] [iOSSim] Done waiting extra time for simulator
[iOSSim] Simulator booted in 26076ms// 安装TestApp、开启WebDriverAgent
[debug] [XCUITest] Installing app '/Users/zhiyu.zhao/Desktop/appiumTest/sample-code/sample-code/examples/java/junit/../../../apps/TestApp/build/release-iphonesimulator/TestApp.app' on device
[XCUITest] Using default agent: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgent.xcodeproj
[XCUITest] Using default bootstrap: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent
[XCUITest] Launching WebDriverAgent on the device
[debug] [XCUITest] Carthage found: /usr/local/bin/carthage
[debug] [XCUITest] Killing hanging processes
[debug] [XCUITest] Beginning test with command 'xcodebuild build test -project /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination id=B9747B0B-C664-4C44-A651-9C3D8F7A980A -configuration Debug' in directory '/usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent'
[XCUITest] Waiting for WebDriverAgent to start on device[debug] [WebDriverAgent] Sim: Nov 2 16:50:33 999999999 CoreSimulatorBridge[56212]: Pasteboard change listener callback port <NSMachPort: 0x7ff8707030a0> registered[Xcode] === BUILD TARGET WebDriverAgentLib OF PROJECT WebDriverAgent WITH CONFIGURATION Debug ===[Xcode] === BUILD TARGET WebDriverAgentRunner OF PROJECT WebDriverAgent WITH CONFIGURATION Debug ===// WebDriverAgentRunner-Runner.app
[debug] [WebDriverAgent] Sim: Nov 2 16:50:42 999999999 CoreSimulatorBridge[56212]: Requesting installation of file:///Users/zhiyu.zhao/Library/Developer/Xcode/DerivedData/WebDriverAgent-brdadhpuduowllgivnnvuygpwhzy/Build/Products/Debug-iphonesimulator/WebDriverAgentRunner-Runner.app/ with options: {[debug] [WebDriverAgent] Sim: Nov 2 16:50:46 999999999 CoreSimulatorBridge[56212]: [Common] [FBSSystemService][0xc2a4] Sending request to open "com.apple.test.WebDriverAgentRunner-Runner"
[debug] [WebDriverAgent] Sim: Nov 2 16:50:46 999999999 CoreSimulatorBridge[56212]: [Common] [FBSSystemService][0xc2a4] Request successful: <FBSProcessHandle: 0x7ff870505830; XCTRunner:56563; valid: YES>
[debug] [WebDriverAgent] Sim: Nov 2 16:50:47 999999999 XCTRunner[56563]: assertion failed: 15G31 14A345: libxpc.dylib + 62597 [37A9DF49-35C1-3D93-B854-B35CACF0100F]: 0x7d
[debug] [WebDriverAgent] Sim: Nov 2 16:50:47 999999999 XCTRunner[56563]: Running tests...
[debug] [WebDriverAgent] Sim: Nov 2 16:50:48 --- last message repeated 4 times ---
[debug] [WebDriverAgent] Sim: Nov 2 16:50:48 999999999 XCTRunner[56563]: Continuing to run tests in the background with task ID 1
[debug] [WebDriverAgent] Sim: Nov 2 16:50:48 --- last message repeated 13 times ---
[debug] [WebDriverAgent] Sim: Nov 2 16:50:48 999999999 XCTRunner[56563]: Built at Nov 2 2016 16:50:41[XCUITest] Detected that WebDriverAgent is running at url 'http://10.86.132.138:8100'
[debug] [WebDriverAgent] Sim: Nov 2 16:50:48 999999999 XCTRunner[56563]: ServerURLHere->http://10.86.132.138:8100<-ServerURLHere
[XCUITest] WebDriverAgent started at url 'http://10.86.132.138:8100'[JSONWP Proxy] Proxying [POST /session] to [POST http://localhost:8100/session] with body: {"desiredCapabilities":{"bundleId":"io.appium.TestApp","arguments":[],"environment":{},"shouldWaitForQuiescence":true}}
[JSONWP Proxy] Got response with status 200: {"value":{"sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708","capabilities":{"device":"iphone","browserName":"TestApp","sdkVersion":"10.0","CFBundleIdentifier":"io.appium.TestApp"}},"sessionId":"79CBBB84-DB6E-48BA-B79F-91539E1E4708","status":0}[Appium] New XCUITestDriver session created successfully, session 94f7526c-94ba-4ece-8740-d94bd3d4f50f added to master session list
[MJSONWP] Responding to client with driver.createSession() result: {"webStorageEnabled":false,"locationContextEnabled":false,"browserName":"","platform":"MAC","javascriptEnabled":true,"databaseEnabled":false,"takesScreenshot":true,"networkConnectionEnabled":false,"app":"/Users/zhiyu.zhao/Desktop/appiumTest/sample...
[HTTP] <-- POST /wd/hub/session 200 59127 ms - 520
Client发送建立Session请求->Server接受,打开模拟器->安装被测试APP->启动WDARunner->完成后Device返给Server Device Session id->Server返给Client 设备信息和本次Session id
5.ATX:AutomatorX
项目GITHUB
特点
完全的黑盒测试框架,无需知道项目代码,非侵入式
支持iOS, Android的自动化测试,两个平台都支持测试第三方应用
对于iOS的真机,安卓模拟器都能很好的支持
可以用来测试Windows应用
对于游戏的测试使用了图像识别
同一个测试脚本可以通过图像的缩放算法,适配到其他分辨率的手机上
主要特点集中在图像识别上,通过图像识别来寻找某控件和页面状态判断的断言。
6.最终目标
小目标3.0让自动化自动起来,也就是搭建云测试平台,实现设备和测试脚本分离、透明化。同一App的测试脚本可以由多个Client来编写,然后由平台合理分配设备资源来运行这些测试脚本。而由于UI界面本身多变的特性,脚本的维护会比接口的自动化测试成本高很多,所以最终目标是在3.0的基础上,在Client端加上屏幕录制技术,类似于Xcode的录制操作生成代码的功能。这样就能够建立起一整套维护成本低,自动化程度高,拓展性好的自动化测试平台。
相关文章:
iOS UI自动化测试详解
前言: 小目标 关于UI自动化的定义,我想要的是自动地按照流程去点击页面、输入数据,不需要人去参与,节省人工时间。比如登录,能够自己去填写用户名&密码,然后点击按钮跳转到下一个页面等。在能够保证业…...
Mybatis源码分析(九)Mybatis的PreparedStatement
文章目录一 JDBC的PreparedStatement二 prepareStatement的准备阶段2.1 获取Connection2.1.1 **UnpooledDataSource**2.1.2 PooledDataSource2.2 Sql的预编译PreparedStatementHandler2.3 为Statement设置参数2.4 执行具体的语句过程官网:mybatis – MyBatis 3 | 简…...
winfrom ui
http://www.iqidi.com/download/warehouse/Device_DotNetBar.rar http://qiosdevsuite.com/Download https://sourceforge.net/projects/qiosdevsuite/ https://www.cnblogs.com/hcyblogs/p/6758381.html https://www.cnblogs.com/jordonin/p/6484366.html MBTiles地图瓦片管…...
中国国家级地面气象站基本气象要素日值数据集(V3.0)
数据集摘要 数据集包含了中国基本气象站、基准气候站、一般气象站在内的主要2474个站点1951年1月以来本站气压、气温、降水量、蒸发量、相对湿度、风向风速、日照时数和0cm地温要素的日值数据。数据量为21.3GB。 (1)SURF_CLI_CHN_MUL_DAY-TEM-12001-201501.TXT 气温数据TEM, 包…...
【Python语言基础】——Python NumPy 数组副本 vs 视图
Python语言基础——Python NumPy 数组副本 vs 视图 文章目录 Python语言基础——Python NumPy 数组副本 vs 视图一、Python NumPy 数组副本 vs 视图一、Python NumPy 数组副本 vs 视图 副本和视图之间的区别 副本和数组视图之间的主要区别在于副本是一个新数组,而这个视图只是…...
Spring Cloud_OpenFeign服务接口调用
目录一、概述1.OpenFeign是什么2.能干嘛二、OpenFeign使用步骤1.接口注解2.新建Module3.POM4.YML5.主启动类6.业务类7.测试8.小总结三、OpenFeign超时控制1.超时设置,故意设置超时演示出错情况2.是什么3.YML中需要开启OpenFeign客户端超时控制四、OpenFeign日志打印…...
十三、GIO GTask
GTask表示管理一个可取消的“任务task” GCancellable GCancellable是一个线程安全的操作取消栈,用于整个GIO,以允许取消同步和异步操作。 它继承于GObject对象,不是一个单纯的结构体 相关函数 g_task_new GTask* g_task_new (GObject*…...
ch4_1存储器
1. 存储器的类型 1.1 按照存储介质来分类 半导体存储器: TTL, MOS 易失性 磁表面存储器: 磁头, 载磁体; 磁芯存储器: 硬磁材料, 环状元件 光盘存储器: 激光, 磁光材料; 1.2 按…...
Doris通过Flink CDC接入MySQL实战
1. 创建MySQL库表,写入demo数据 登录测试MySQL mysql -u root -pnew_password创建MySQL库表,写入demo数据 CREATE DATABASE emp_1;USE emp_1; CREATE TABLE employees_1 (emp_no INT NOT NULL,birth_date DATE NOT NULL,…...
搭建zookeeper高可用集群详细步骤
目录 一、虚拟机设置 1.新建一台虚拟机并克隆三台,配置自定义 2.修改四台虚拟机的主机名并立即生效 3.修改四台虚拟机的网络信息 4.重启四台虚拟机的网络服务并测试网络连接 5.重启四台虚拟机,启动后关闭四台虚拟机的防火墙 6.在第一台虚拟机的/e…...
Scala 变量和数据类型(第二章)
第二章、变量和数据类型2.1 注释2.2 变量和常量(重点)2.3 标识符的命名规范2.4 字符串输出2.5 键盘输入2.6 数据类型(重点)回顾:Java数据类型Scala数据类型2.7 整数类型(Byte、Short、Int、Long)…...
【JVM基础内容速查表】JVM基础知识 默认参数 GC命令 工具使用 JVM参数设置、说明、使用方法、注意事项等(持续更新)
目录一、JVM前置知识1. -X、-XX含义2. JVM参数值的类型和设置方式3. 查看GC时用到的命令和JVM参数4. 查看JVM默认参数二、垃圾收集器选择-XX:UseSerialGC-XX:UseParallelGC-XX:UseParallelOldGC-XX:UseParNewGC-XX:UseConcMarkSweepGC-XX:UseG1GC三、垃圾收集器特有参数1. ParN…...
C语言经典编程题100例(61~80)
目录61、练习7-7 矩阵运算62、练习7-8 方阵循环右移63、习题6-1 分类统计字符个数64、习题6-2 使用函数求特殊a串数列和65、习题6-4 使用函数输出指定范围内的Fibonacci数66、习题6-5 使用函数验证哥德巴赫猜想67、习题6-6 使用函数输出一个整数的逆序数68、练习8-2 计算两数的…...
toxssin:一款功能强大的XSS漏洞扫描利用和Payload生成工具
关于toxssin toxssin是一款功能强大的XSS漏洞扫描利用和Payload生成工具,这款渗透测试工具能够帮助广大研究人员自动扫描、检测和利用跨站脚本XSS漏洞。该工具由一台HTTPS服务器组成,这台服务器将充当一个解释器,用于处理恶意JavaScript Pay…...
Keepalived与HaProxy的协调合作原理分析
Keepalived与HaProxy的协调合作原理分析keepalived与haproxy合作场景更好的理解方式协调合作中考虑的问题一、Keepalived以TCP/IP模型角度来分析:二、HaProxy总结:协调合作中考虑的问题的答案虚拟ip:虚拟IP技术,就是一个未分配给客…...
抖音如何找到博主视频推广?筛选博主要看那些数据
近年来抖音视频推广越来越成为企业宣传的热门选择,今天就来和大家聊聊抖音如何找到博主视频推广,以及几种主流的对接方式。一、什么是抖音博主视频推广?抖音博主视频推广就是通过博主的影响力和粉丝量,吸引用户到短视频平台进行观看相关合作…...
Win11的两个实用技巧系列之如何关闭登录密码?
Win11如何关闭登录密码?Win11关闭登录密码的两种解决方法win11是电脑更新后的全新系统,每次开启需要输入密码。有的用户嫌麻烦想要关闭,下面小编就为大家带来了关闭的方法,一起来看看吧有不少用户在升级或者第一次使用Win11系统的时候&#…...
润普挂卷失败之老卷宗对接NP无法获取案件信息问题排查
润普挂卷失败之老卷宗对接NP无法获取案件信息问题排查 写在最前面 根因:NP的dzjzzzfw与老卷宗dzjz服务用的zookeeper不是同一个,且老卷宗指向的zookeeper没有任何一个匹配的dzjzzzfw。仅有消费者,没有任何生产者,导致老卷宗通过…...
产品经理面试题思考及回答思路(一)
求职产品助理/经理岗位,转行产品岗面试真题 关于产品经理岗位能力的思考: 什么是产品经理?为什么要当/选择做产品经理?怎么理解产品经理?如何理解产品经理的价值?产品日常工作有哪些?工作流程…...
Routability-Driven Macro Placement with Embedded CNN-Based Prediction Model
Routability-Driven Macro Placement with Embedded CNN-Based Prediction Model 2019 Design, Automation & Test in Europe Conference & Exhibition (DATE) DOI: 10.23919/DATE.2019.8715126 目录Abstract一、Introduction二、PROBLEM FORMULATION AND PRELIMINARIE…...
论一个上班族如何一次性通过PMP考试
PMP是我工作后考取的一个证书。从准备到通过,花了大约三个月的时间。我之前在某家互联网公司从事程序员的工作,工作一段时间后,天天敲着代码,改着bug,感觉比较迷茫,不知道未来的发展在哪里,都说…...
Web前端:使用Angular CLI时的最佳实践和专业技巧
在web开发业务中,构建高性能的应用程序是首要因素。此外,用开发人员最流行的语言开发一个健壮的网站将始终为构建高功能的网站提供适当的基础网站。相比之下,不可否认,Angular CLI是建立得最好且正在成长的框架之一。Angular CLI简…...
从0到1一步一步玩转openEuler--15 openEuler使用DNF管理软件包
文章目录15.1 搜索软件包15.2 列出软件包清单15.3 显示RPM包信息15.4 安装RPM包15.5 下载软件包15.6 删除软件包DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查询软件包信息,从指定软件库获取软件包,自动处理依赖关系以安装或卸…...
【java】Spring Boot --spring boot项目整合xxl-job
文章目录1、源码下载地址2.文档地址3.源码结构4.初始化数据库脚本5.配置调度中心xxl-job-admin5.1 修改调度中心配置文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties5.2 启动调度中心5.3 访问调度中心管理界面6.创建执行器项目6.3 载入配置…...
视图、索引、存储过程、触发器
视图、索引、存储过程、触发器 group by补充: 规范来说,分组查询中,select后的字段只能是group by的字段或者是聚合函数。mysql在这有一个小优化,分组后如果某个字段的所有记录相同,同样可以select。 视图 视图是虚拟…...
ImportError: cannot import name ‘FlattenObservation‘ from ‘gym.wrappers‘ 解决方案
问题描述 今天在运行openai给出的ppo2的baseline的时候遇到了以下bug: File "/root/code/baselines_openai/baselines/common/cmd_util.py", line 12, in <module> from gym.wrappers import FlattenObservation, FilterObservation ImportErr…...
大件传输的9种方法
不知道你有没有试过用电子邮件进行大文件传输,由于文件大小的限制,往往会发送失败。同时,一些文件共享服务对传输的文件有大小限制,使得你无法与朋友分享电影片段或向客户展示你的工作样本。还有一些要求你注册一个账户࿰…...
将vue2的项目《后台管理模式》转变为vue3版本 (一)
本篇主要讲了将v2项目转变为v3版本,以本人经验愿于各位分享 希望大家可以一起交流!!!! 文章目录一、app 出口位置二 、 index.js 路由配置三、package.json 文件四、 main.js 既然安装插件那就需要引入五、 跨域问题总…...
苹果手机怎么下载手机铃声?图文教程,快速学会
很多小伙伴喜欢使用苹果手机,可是苹果手机里的铃声自己并不是很喜欢听,想要下载一些好听的歌曲更换自己的手机铃声。苹果手机怎么下载手机铃声?别着急,今天小编以图文的方式,教教大家如何使用苹果手机下载手机铃声。 苹…...
AJAX笔记(二)Fetch和axios
1、Fetch 1.1、XMLHttpRequest的缺陷 1.2、fetch的get写法 1.3、fetch的post写法 1.4、fetch的put写法 1.5、fetch的patch写法 1.6、fetch的delete写法 2、axios 2.1、axios的介绍 2.2、axios的get写法 2.3、axios的post写法(图一json写法和图二三form写法&#x…...
网站打开出现建设中/搜索引擎优化的简称是
虚拟化由于其带来的维护费用的大幅降低而受到追捧,如能减少服务器占用空间,降低购买软硬件设备的成本,大幅度提高系统的利用率。然而对其安全问题,人们也一直在争论不休,一方观点认为虚拟化技术能有效提升系统的安全性…...
泰安做网站公司哪家好/seo顾问服务 乐云践新专家
目录注释变量和常量标识符的命名规范字符串输出键盘输入数据类型整数类型浮点类型字符类型布尔类型Unit类型,Null类型和Nothing类型(重点)Unit类型Null类型Nothing类型类型转换数值类型自动转换强制类型转换注释 scala的注释的使用跟JAVA是一…...
商丘网站/深圳seo网络推广
蓝桥杯 --- 二分与前缀和(习题)730. 机器人跳跃问题1221. 四平方和1227. 分巧克力99. 激光炸弹1230. K倍区间730. 机器人跳跃问题 机器人正在玩一个古老的基于 DOS 的游戏。 游戏中有 N1 座建筑——从 0 到 N 编号,从左到右排列。 编号为 …...
番禺做网站600元/seo研究协会网
写在文章之前:博友们,你的支持是我最大的动力,在阅读我文章的同时,也请为我投上你宝贵的一票,谢谢。 投票请进:http://2010blog.51cto.com/855319 ----&#…...
川沙网站建设/竞价推广培训课程
wpf阻止键盘快捷键altspace,altF4 原文:wpf阻止键盘快捷键altspace,altF4/// <summary> /// 阻止 altf4和altspace 按键 /// </summary> /// <param name"e"></param> protected overrid…...
东营网站建设方案/营销方案怎么写
随着微软Visual Studio 2010和.NET Framework 4.0的推出,微软向开发人员提供了创建多线程应用程序的更好的工具和类库。在这篇文章中,笔者将为您介绍Concurrency Visualizer的新功能是什么以及它能够提供什么类型的信息。 要把Visual Studio 2010的Conc…...