21

iOS Appium自动化测试框架原理简析 - 大伟不是戴维

 4 years ago
source link: https://easeapi.com/blog/blog/103-appium.html?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Appium是目前比较好用的跨平台自动化测试框架,在iOS端采用WebDriverAgent作为webdriver驱动,实现了自动化脚本编写到运行的全流程覆盖。

在Xcode 8之前,基于UI Automation的自动化测试方案是比较好用且非常流行的。但在Xcode 8之后,苹果在instruments工具集中直接废除了Automation组件,转而支持使用UI Testing。

UI Testing

从Xcode 7开始,苹果提供了UI Testing框架,也就是我们在APP test工程中使用的XCTest的那一套东西。UI Testing包含几个重要的类,分别是XCUIApplication、XCUIElement、XCUIElementQuery。

  • XCUIApplication

代表正在测试的应用程序的实例,可以对APP进行启动、终止、传入参数等操作。

- (void)launch;
- (void)activate;
- (void)terminate;
@property (nonatomic, copy) NSArray <NSString *> *launchArguments;
@property (nonatomic, copy) NSDictionary <NSString *, NSString *> *launchEnvironment;

XCUIApplication在iOS上提供了两个初始化接口:

//Returns a proxy for the application specified by the "Target Application" target setting.
- (instancetype)init NS_DESIGNATED_INITIALIZER;

//Returns a proxy for an application associated with the specified bundle identifier.
- (instancetype)initWithBundleIdentifier:(NSString *)bundleIdentifier NS_DESIGNATED_INITIALIZER;

其中initWithBundleIdentifier接口允许传入一个bundle id来操作指定APP。这个技术点是iOS APP能够自动化测试的关键所在。

  • XCUIElement

表示界面上显示的UI元素。

  • XCUIElementQuery

用于定位UI元素的查询对象。

上述几个模块就是一个UI测试框架的核心能力,后面在写Appium的自动化脚本时也是一样的套路:启动APP->定位UI元素->触发操作。

WebDriverAgent

WebDriverAgent是Facebook开发的基于XCTest.framework的开源项目,实现了在iOS上支持WebDriver协议的服务,可以用来启动/终止APP,点击/滑动页面。

webdriver协议是一套基于HTTP协议的JSON格式规范,协议规定了不同操作对应的格式。之所以需要这层协议,是因为iOS、Android、浏览器等都有自己的UI交互方式,通过这层”驱动层“屏蔽各平台的差异,就可以通过相同的方式进行自动化的UI操作,做网络爬虫常用的selenium是浏览器上实现webdriver的驱动,而WebDriverAgent则是iOS上实现webdriver的驱动。

使用Xcode打开WebDriverAgent项目,连接上iPhone设备之后,选中WebDriverAgentRunner->Product->Test,则会在iPhone上安装一个名为WebDriverAgentRunner的APP,这个APP实际上是一个后台应用,直接点击ICON打开的话会退出。

具体到代码层面,WebDriverAgentRunner的入口在UITestingUITests.m文件

- (void)testRunner
{
  FBWebServer *webServer = [[FBWebServer alloc] init];
  webServer.delegate = self;
  [webServer startServing];
}

会在手机上6100端口启动一个HTTP server,startServing方法内部就是一个死循环,监听网络传输过来的webdriver协议的数据,解析并处理点击事件。

有意思的是,WebDriverAgent并没有使用XCUIApplication的initWithBundleIdentifier方法,而是使用了initPrivateWithPath的私有方法。测试发现两者效果上没什么区别,这里暂时不清楚为什么不直接使用initWithBundleIdentifier。

WebDriverAgent如何处理点击事件的?

从WebDriverAgent的源码可以清晰的看到,在Commands目录,是支持的操作类集合。每一个操作都通过routes类方法注册对应的路由和处理该路由的函数。手势处理在FBTouchActionCommands.m中,

+ (NSArray *)routes
{
  return
  @[
    [[FBRoute POST:@"/wda/touch/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)],
    [[FBRoute POST:@"/wda/touch/multi/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)],
    [[FBRoute POST:@"/actions"] respondWithTarget:self action:@selector(handlePerformW3CTouchActions:)],
  ];
}

可以看到/wda/touch/perform、/wda/touch/multi/perform、/actions路由负责处理不同的点击事件。那么当一个点击的url请求过来时,如何转化为iOS的UIEvent事件呢?跟踪代码发现核心代码是:

[[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:record completion:(id)^(BOOL result, NSError *invokeError) {
    handlerBlock(record, invokeError);
}];

XCUIDevice的eventSynthesizer是私有方法,通过synthesizeEvent发送XCSynthesizedEventRecord(也是私有类)事件。到这里WebDriverAgent的流程就很清除了。实际上由于使用了很多私有方法,WebDriverAgent并非仅能自动化当前APP,也是可以操作手机屏幕以及任意APP的。

Appium

上面介绍的UI Testing和WebDriverAgent都是iOS上的内容。实际上,在更多的时候我们需要考虑跨平台,从笔者调研来看,现阶段比较好的方案是Appium。Appium是一个开源测试自动化框架,可用于iOS、Android和web应用程序测试,支持python、JAVA、php等多种语言编写测试用例。Appium包含客户端和服务端等模块。

Appium客户端

在iOS上的客户端实际上就是使用了WebDriverAgent,作为实现webdriver协议的驱动层。

Appium服务端

Appium的服务端是一个桌面应用,用于和客户端通信,启动Appium的服务端之后,会在电脑上启动一个默认端口号是4723的HTTP服务。当我们编写完脚本执行时,脚本代码会被转换为webdriver协议的JSON数据,通过HTTP请求发送到电脑的4723端口。Appium服务端将脚本的执行请求下发给客户端(请求客户端的6100端口),客户端同样使用webdriver协议响应。

Appium的部署

安装各种驱动

brew install libimobiledevice  
npm install -g ios-deploy   
#appium-doctor可检查依赖是否安装成功  
npm install -g appium-doctor   
#检查依赖项  
appium-doctor -ios  

安装appium-desktop

下载安装之后,需要对WebDriverAgent修改下证书信息,使其可以真机调试。

cd /Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-youiengine-driver/node_modules/appium-webdriveragent  
./Scripts/bootstrap.sh -d  

连接真机后,打开WebDriverAgent.xcodeproj,Product->Test运行WebDriverAgentRunner(需要配置证书)。终端会显示一个地址 http://169.254.138.98:8100 访问 http://169.254.138.98:8100/status打开有json信息则表示服务连接成功。

需要注意的是,Facebook原始的WebDriverAgent功能上似乎有些问题,appium-desktop自带的WebDriverAgent版本据说是经过优化修改过的,运行正常。

部署完成之后打开Appium,Start Server后即可编写脚本了。一个典型的monkey脚本如下:

#!/usr/bin/python
# coding=utf-8
import unittest
import os
from appium import webdriver
from time  import sleep
from appium.webdriver.common.touch_action import TouchAction
import random

class AppTest(unittest.TestCase):

    def setUp(self):
        print("setUp")
        udid = "c9d719e41f6a9ae00353db126a6561fdf032cc23"
        bundleId = "com.easeapi"

        if False:
            #new app
            app = os.path.abspath('/Users/easeapi.app')
            self.driver = webdriver.Remote(command_executor = 'http://127.0.0.1:4723/wd/hub', desired_capabilities = {'app':app,'platformName': 'iOS', 'platformVersion': '12.4.3', 'deviceName': 'EaseapiPhone', 'bundleId': bundleId, 'udid': udid})
        else:
            #exist app
            self.driver = webdriver.Remote(command_executor = 'http://127.0.0.1:4723/wd/hub', desired_capabilities = {'platformName': 'iOS', 'platformVersion': '12.4.3', 'deviceName': 'EaseapiPhone', 'bundleId': bundleId, 'udid': udid})

    def tearDown(self):
        #self.driver.quit()

    def test_monkey(self):
        __size = self.driver.find_element_by_xpath('//UIAApplication[1]').size
        window_width = __size['width']
        window_height = __size['height']
        while True: 
            sleep(1.0)
            _x = random.uniform(0, window_width)
            _y = random.uniform(0, window_height)
            print('x:%f y:%f') % (_x, _y)
            TouchAction(self.driver).tap(x=_x, y=_y).perform()

if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(AppTest)
    unittest.TextTestRunner(verbosity=2).run(suite)

执行该脚本,即可对bundle id为com.easeapi的app进行monkey测试。如果想自定义事件也比较简单:

button = self.driver.find_element_by_accessibility_id("按钮")
button.click()

很多时候,编写脚本是一件很繁琐无趣的工作,好在Appium提供了录制的功能。我们上面仅仅启动了Appium的服务。实际上通过Appium也是可以直接在电脑上操作手机的。打开Appium,点击搜索按钮,在JSON Representation区域填写如下模板内容:

{
  "platformName": "ios",
  "platformVersion": "12.4.3",
  "udid": "c9d719e41f6a9ae00353db126a6561fdf032cc23",
  "deviceName": "EaseapiPhone",
  "automationName": "XCUITest",
  "bundleId": "com.easeapi",
  "xcodeSigningId": "iPhone Developer"
}

完成后点击Start Session,即可在电脑上直接操作iPhone手机,使用录制功能可以将每一步的点击操作转化为脚本文件,大大较少了开发工作量。

以上就是Appium的实现原理和简单使用介绍,总体来看Appium是一个比较优秀的自动化测试方案,值得推广使用。最后简单说下其他方案的调研结果:

  • fastmonkey:在Xcode11上已无法运行。
  • swiftmonkey:使用Swift3.4开发,最新Xcode已经不支持这个版本的Swift。

iOS DeviceCheck详解
iOS 13 Scene Delegate and multiple windows
iOS Sign With Apple实践
iOS 13中dyld 3的改进和优化
iOS Self-Sizing的一点优化


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK