其他测试框架 Naga——基于 WebDriverAgent 的 iOS 自动化测试实现

adfghzhang · 2017年04月08日 · 5115 次阅读
本帖已被设为精华帖!

Naga 是什么?

Naga
NagaTest

Naga 是个代号,它是基于 java 语言的 iOS 自动化测试实现,支持 iOS 9.x+ 版本的 Native,Hybrid,H5 的 UI 自动化测试。
Naga 非常轻盈,它通过 jar 的方式分发,在你的测试工程中引用 naga.jar 即可开始您的 iOS 自动化测试之旅。
Naga 环境部署简单快捷,较少的依赖能让您快速的完成环境搭建。

为什么会有 Naga?

这个想法开始于今年年初,刚换了新工作总想用工作之余和闲暇时间来做点什么,因为近几年的工作都与移动 UI 自动化测试紧密相关,前后用过 Appium,macaca,还试了一下 ATX。目前的工作中自动化测试的开展都是以 Appium 为主,也使用过一段时间的 macaca。由于大多经验都与之相关,所以谋发出了编写一个轻量级移动自动化测试解决方案的想法。Naga 的一些想法和解决方案均来源于这两个优秀的开源框架。

Naga 的工作原理

Naga 跟 Appium 和 macaca 一样都使用了 WebDriverAgent 来驱动 Native 部分的 iOS 自动化测试,使用 ios_webkit_debug_proxy 配合 selenium-atoms 来驱动 Web 部分。
Naga 工作原理简单示意图:

Naga 环境搭建

  • Mac OS 10.11.5+(推荐 10.12)

  • Xcode 8.2+(在 8.2.1 上测试 OK,未更新 8.3)

  • Jdk 1.7+(推荐 JDK1.8)

  • Brew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • ios-webkit-debug-proxyy
brew install ios-webkit-debug-proxy
  • usbmuxd(真机运行必须安装)
brew install usbmuxd
  • WebDriverAgent

Naga上的 WebDriverAgent.zip 解压并复制到/usr/local/lib/node_modules/目录下即可。如图所示:

Naga API

  • NagaDriver 类
void acceptAlert() 
accept alert

String alertText() 
get the alert text

void context(String context) 
set the context by context name when set WEBVIEW context. you can use the page id or the page tile, such as WEBVIEW_1 or WEBVIEW_title

void dismissAlert() 
dismiss alert

Element findElementByClassName(String className) 
find Element by class name

Element findElementByCssSelector(String css) 
find Element by css selector

Element findElementById(String id) 
find Element by id

Element findElementByLinkText(String linkText) 
find Element by link text

Element findElementByName(String name) 
find Element by name

Element findElementByPartialLinkText(String partialLinkText) 
find Element by partial link text

Element findElementByTagName(String tagName) 
find Element by tag name

Element findElementByXPath(String xpath) 
find Element by xpath

Elements findElementsByClassName(String className) 
find Elements by class name

Elements findElementsByCssSelector(String css) 
find Elements by css selector

Elements findElementsById(String id) 
find Elements by id

Elements findElementsByLinkText(String linkText) 
find Elements by link text

Elements findElementsByName(String name) 
find Elements by name

Elements findElementsByPartialLinkText(String partialLinkText) 
find Elements by partial link text

Elements findElementsByTagName(String tagName) 
find Elements by tag name

Elements findElementsByXPath(String xpath) 
find Elements by xpath

java.util.List<String> getContexts() 
get contexts

String getCurrentContext() 
get current context name

void hideKeyboard() 
hide the keyboard. The keyboard on iPhone cannot be dismissed because of a known XCTest issue.

void home() 
click home

NagaDriver initDriver(JSONObject capabilities) 
init NagaDriver

void quit() 
quit driver

void saveScreenShot(String fileName) 
save the screenshot to a file

String screenShot() 
get the screenshot base64 code

String sessionId() 
get the current session id

void sleep(long sec)  
String source() 
get page source

Integer status() 
get session status

void swipe(double startx, double starty, double endx, double endy, double duruation) 
drag the screen from start location to end location

void tap(double x, double y) 
tap the screen by location

void toUrl(String url) 
go to the website by url WEBVIEW ONLY

JSONObject window_size() 
get the screen size 


  • Element 类
void clear() 
clear the Element text

void click() 
click the Element

Element findElementByClassName(String className) 
find the child element NATIVE ONLY

Element findElementByCssSelector(String cssSelector) 
find the child element NATIVE ONLY

Element findElementById(String id) 
find the child element NATIVE ONLY

Element findElementByLinkText(String linkText) 
find the child element NATIVE ONLY

Element findElementByName(String name) 
find the child element NATIVE ONLY

Element findElementByPartialLinkText(String partialLinkText) 
find the child element NATIVE ONLY

Element findElementByTagName(String tagName) 
find the child element NATIVE ONLY

Element findElementByXPath(String xpath) 
find the child element NATIVE ONLY

Elements findElementsByClassName(String className) 
find the child elements NATIVE ONLY

Elements findElementsByCssSelector(String cssSelector) 
find the child elements NATIVE ONLY

Elements findElementsById(String id) 
find the child elements NATIVE ONLY

Elements findElementsByLinkText(String linkText) 
find the child elements NATIVE ONLY

Elements findElementsByName(String name) 
find the child elements NATIVE ONLY

Elements findElementsByPartialLinkText(String partialLinkText) 
find the child elements NATIVE ONLY

Elements findElementsByTagName(String tagName) 
find the child elements NATIVE ONLY

Elements findElementsByXPath(String xpath) 
find the child elements NATIVE ONLY

String getAttribute(String name) 
get the attribute from the Element by attribute name

String getComputedCss(String name) 
get computed css from Element WEBVIEW only

JSONObject getRect() 
get rect by Element

String getText() 
get the Element text

boolean isDisplayed() 
get the Element is display or not

void sendKeys(String text) 
send text to the Element 
  • Elements 类
Element get(int index) 
get the Element for the Elements by index

int size() 
get size of this Elements 

在真机上测试

  • WebDriverAgent 签名

首先需要对 WebDriverAgent 进行签名(普通 AppleId 即可,推荐使用开发者账号签名避免个人账号签名过期的问题),需要对 WebDriverAgentLib 和 WebDriverAgentRunner 都进行签名,签名完成后 Build 校验签名是否正确。
推荐使用 Xcode--Product--Test 在真机上安装上 WebDriverAgentRunner。第一次安装时如使用个人账号签名需要在设置 -- 通用 -- 描述文件与设备管理中信任你签名的账号,再次安装方可成功。

  • 被测应用

被测试的 app 可以是从 appstore 下载的 app(需要知道该 app 的 bundleId),也可以是签名过的 app(签名步骤同 WebDriverAgent 签名),如需要安装 app,可在 initDriver 时传入 app 参数,如已安装 app 可使用 bundleId 参数。(暂未实现是否卸载再安装的方法,均使用 WebDriverAgent 原生方案)

NagaTest

使用 xdf 提供的ios-app-bootstrap项目做测试示例,如需要在真机测试请下载该工程对其进行签名后使用。
更多测试示例请前往NagaTest

NagaDriver driver = new NagaDriver();

@Before
public void setUp() throws Exception {
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("bundleId", "xdf.ios-app-bootstrap");
    jsonObject.put("app", System.getProperty("user.dir") + "/apps/ios-app-bootstrap.app");
    JSONObject desiredCapabilities = new JSONObject();
    desiredCapabilities.put("port", "8901");
    desiredCapabilities.put("udid", "9CC1ADCC-94CE-47C3-AA4F-5008AE463119");
    desiredCapabilities.put("desiredCapabilities", jsonObject);
    driver.initDriver(desiredCapabilities);
}

@Test
public void test_hybrid() throws Exception {
    // set the screenshot image where to save
    String imageFilepath = System.getProperty("user.dir");

    System.out.println("------------#1 login test-------------------");

    driver.findElementByClassName("XCUIElementTypeTextField").sendKeys("中文+Test+12345678");
    driver.findElementByXPath("//XCUIElementTypeSecureTextField[1]").sendKeys("111111");
    driver.findElementByName("Done").click();
    driver.findElementByName("Login").click();
    driver.sleep(1);

    System.out.println("------------#2 scroll tableview test-------------------");

    driver.findElementByName("HOME").click();
    driver.findElementByName("list").click();
    driver.sleep(1);
    driver.swipe(200, 420, 200, 120, 1);
    driver.sleep(2);

    System.out.println("------------#3 webview test-------------------");

    driver.findElementByName("Webview").click();
    driver.sleep(3);
    // save screen shot
    driver.saveScreenShot(imageFilepath + "/webView.png");

    driver.context("WEBVIEW_test");
    driver.sleep(2);
    driver.findElementById("pushView").click();
    driver.sleep(3);
    driver.findElementById("popView").click();
    driver.sleep(2);

    System.out.println("------------#4 baidu web test-------------------");
    switchToNative();
    driver.findElementByName("Baidu").click();
    driver.sleep(5);
    driver.saveScreenShot(imageFilepath + "/baidu.png");

    driver.context("WEBVIEW_百度一下");
    // driver.context("WEBVIEW_2");
    driver.findElementById("index-kw").sendKeys("中文+TesterHome");
    driver.findElementById("index-bn").click();
    driver.sleep(5);
    System.out.println(driver.source());

    System.out.println("------------#5 logout test-------------------");

    switchToNative();
    driver.findElementByName("PERSONAL").click();
    driver.sleep(1);
    driver.findElementByName("Logout").click();
    driver.sleep(1);
}

// switch to native
public void switchToNative() throws Exception {
    driver.context("NATIVE_APP");
}

@After
public void tearDown() throws Exception {
    driver.quit();
}

Naga 征集试用用户

Naga 还是位嗷嗷待哺的婴儿,需要各位有兴趣的朋友加入试用并反馈试用感受,我会非常认真的处理每一位的试用结果和感受(对它的成长帮助非常大的我还会给出一定的红包奖励)。
在此征集第一批试用人员,要求如下:
1.您需要一颗爱心(它太稚嫩了,可能有很多不足甚至奇奇怪怪的 bug);
2.您需要一台有 Mac OS X 10.11 或 Mac OS 10.12 系统的电脑(虚拟机亦可);
3.您需要有一定的 java 编程基础;
有兴趣的朋友欢迎您加入

Naga 的未来

这是我的第一个个人项目,我一定会努力将它做好。
Naga 未来会考虑加入对 Android 的支持,先做好 iOS 部分。
Naga 暂时不会公开源码,分发的 naga.jar 也经过了一点简单的混淆(不过还原难度不大,有兴趣的朋友可以反编译看看)。

2017.04.13 更新

支持 Xcode8.3.1 和 iOS10.3 设备的测试支持;
更新了 WebDriverAgent,请下载WebDriverAgent-iOS10.3.zip

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 17 条回复 时间 点赞
恒温 将本帖设为了精华贴 04月09日 02:13

想法挺好的。问下,为何重新再造个轮子,这个 naga 跟 macaca 以及 appium 有什么不同?

原理上都是相同的,关于为什么要重复造轮子对我个人而言算是算是技术研究吧。不过为什么纠结于重复造轮子的问题呢

adfghzhang 回复

嗯嗯,支持,希望能成为一个更好的自动化测试解决方案。

匿名 #14 · 2017年04月10日

这个我能简单的理解为实现了一个 JAVA 版本的 webdriverAgentRunner 客户端吗?

匿名 #6 · 2017年04月10日

就是 client

可以这么理解,不过不完全是,WDA 只能支持 native 部分,web 部分还需要别的模块驱动

匿名 #8 · 2017年04月10日
adfghzhang 回复

嗯嗯,感谢,期待你这个会越来越好~

谢谢🙏

请加我入群,我想学习。

loneyao 回复

欢迎

有点重复造轮子的意思,不过赞一个,

peter 回复

谢谢,不用纠结轮子的问题了。

给造轮子的精神,点个赞

quit() 是实现的关闭 iOS app 功能吗?能分享下这段源码否

aaron_qi 回复

quit 其实是点了 home 键,然后做了一些服务的断开功能。暂未加入关闭 app 的功能

辩名识人,加油加油。

匿名 #18 · 2017年09月29日

兄弟 你这 java 的编码风格是 js 的风格呀

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册