[基于 Node.js 的自动化测试-Macaca] – 如何查找界面元素 [终极篇]

本帖已被设为精华帖!,

上一篇 – Macaca 是如何封装 ADB 的

Macaca App Inspector 原理解析 https://testerhome.com/topics/8896

如下内容已过期。


最近很多 Macaca 的用户都会问关于如何查找 Native 界面元素,如何找到映射的类名,如何定位 Webview 或者传统网页的 tag 元素,今天花些时间把原理、经验等做个总结。

首先介绍 Android 平台的做法,AndroidSDK 提供了界面查看器 uiautomatorviewer,我们一探究竟。

UI Automator Viewer

先说几句

熟悉 Android 平台开发和测试的同学应该都清楚 uiautomatorviewer 的位置,首先需要通过模拟器启动一个设备,或者连接真机。

# 如下即可启动 uiautomatorviewer 的可执行文件
$ $ANDROID_HOME/tools/uiautomatorviewer

我们就可以看到初始界面了

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

实现原理

同级目录下的 lib 目录下可以找到 uiautomatorviewer.jar,可以通过反编译(也可以直接找到源代码)查看下里面的实现,其实很简单。

大体原理如下:

初始化 ADB 调试协议

Dump 设备当前的 HierarchyXml,可以发现它将 UI 的描述 xml 存放在系统的临时目录

# 默认大家都有 node 环境,我们来输出一下临时目录
$ node -e "console.log(require('os').tmpdir())"

> /var/folders/qf/gtyrygk530ndzp6crby9phv40000gn/T

可以看到生成的临时目录下有当前的截图图片,和一个 .uix 文件。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

.uix 就是当前界面的 xml 描述,可以看到上面有基本的 bounds, package, class 等字段。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

加载、解析 xml 树,并将相应结构渲染到截图上方,以此来相应用户的鼠标操作

相应用户操作也比较处理,常规的 2d 渲染,加事件捕捉即可,可以用我之前写的一个引擎来感受下这个操作。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

在线 demo 中的实现是一个 canvas 元素,其实可以看出,uiautomatorviewer 的实现也是个 canvas:

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

渲染和事件捕获的原理说差不多了,接下来就容易理解用法和操作了。

用法

我们把鼠标移到想要查看的元素上方,右侧就可以看到类名了,非常简单的操作,可以发现右侧的展示树已经把类名的前缀都去掉了:

// com.android.uiautomator.tree.UINode

private void updateDisplayName() {
String className = (String)this.mAttributes.get("class");
if(className != null) {
String text = (String)this.mAttributes.get("text");
if(text != null) {
String contentDescription = (String)this.mAttributes.get("content-desc");
if(contentDescription != null) {
String index = (String)this.mAttributes.get("index");
if(index != null) {
String bounds = (String)this.mAttributes.get("bounds");
if(bounds != null) {
className = className.replace("android.widget.", "");
className = className.replace("android.view.", "");
StringBuilder builder = new StringBuilder();
builder.append('(');
builder.append(index);
builder.append(") ");
builder.append(className);
if(!text.isEmpty()) {
builder.append(':');
builder.append(text);
}

if(!contentDescription.isEmpty()) {
builder.append(" {");
builder.append(contentDescription);
builder.append('}');
}

builder.append(' ');
builder.append(bounds);
this.mDisplayName = builder.toString();
}
}
}
}
}
}

写一个测试用例

通过 uiautomatorviewer,我们可以用如下的代码拿到一个输入框控件:

elementByNameelementByClassName 是最常用的两个获取元素的 API,我们尽量避免在 Android 上使用 Xpath,而是使用 elementByClassName 来做到。

// 这里是一个最简单的登录界面的登录封装

module.exports = function(username, password) {
return this
.waitForElementsByClassName('android.widget.EditText', {}, 120000)
.then(function(els) {
return els[0];
})
.sendKeys(username)
.sleep(1000)
.elementsByClassName('android.widget.EditText')
.then(function(els) {
return els[1];
})
.sendKeys(password)
.sleep(1000)
.waitForElementByName('Login')
.click()
.sleep(5000);
};

这里对应的 layout 代码如下:

<RelativeLayout
android:id="@+id/topline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="120dp"
android:background="@drawable/login_layout_bg"
android:orientation="horizontal"
android:weightSum="1">

<EditText
android:id="@+id/mobileNoEditText"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="2dp"
android:layout_weight="0.52"
android:background="@null"
android:digits="0123456789"
android:hint="@string/username_tips"
android:inputType="textNoSuggestions"
android:orientation="horizontal"
android:phoneNumber="true"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="15dp" />

</RelativeLayout>

更理想的实现

期待有同学使用 Electron,做一个更轻的实现,直接在浏览器浏览和相应用户操作,同时使用 Inspector 查看映射的结构,使用体验就更佳了。

不过目前官方的轮子已经足够我们用了。

Android APP 中的 Webview

通过打开 Chrome 浏览器调试界面就可以找到 Webview 的页面了。

chrome://inspect/#devices

可以看到通过 Webkit 调试协议查看到的页面,如果你测试的 App 使用了自己修改过的内核或者不是 Webkit 内核,那将无法查看。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

Android 内置的浏览器也支持这样操作,可见每一个连接都是通过包名区分的,有兴趣的可以看 macaca-android 对这里的实现:

Android.prototype.initChromeDriver = function() {
return new Promise((resolve, reject) => {
this.chromedriver = new ChromeDriver();
this.chromedriver.on(ChromeDriver.EVENT_READY, data => {
logger.info(`chromedriver ready with: ${JSON.stringify(data)}`);
resolve('');
});
this.chromedriver.start({
chromeOptions: {
androidPackage: this.apkInfo.package,
androidUseRunningApp: true,
androidDeviceSerial: this.udid
}
});
});
};

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

通过 Inspector 调试器就可以查找和定位元素。


Inspector 如何使用

如果你是前端同学,可以绕过这里了,哈哈。

通过右键 Copy -> Copy selector 或者 Copy -> Copy XPath 可以拷贝这两种选择器的字符参数,然后调用 Macaca 提供的 API 方法即可。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

例如示例 macaca-test-sample 中的:


it('#1 should works with macaca', function() {
return driver
.elementById('kw') // 通过元素 id 获取
.sendKeys('macaca')
.sleep(3000)
.elementById('su')
.click()
.sleep(5000)
.source()
.then(function(html) {
html.should.containEql('macaca');
})
.hasElementByCss('#head > div.head_wrapper') // 通过元素 selector 获取
.then(function(hasHeadWrapper) {
hasHeadWrapper.should.be.true();
})
.elementByXPathOrNull('//*[@id="kw"]') // 通过元素 XPath 获取
.sendKeys(' elementByXPath')
.sleep(3000)
.elementById('su')
.click()
.sleep(5000)
.takeScreenshot();
});

iOS 查找元素

OSX 中提供的 Xcode -> Instruments -> Automation 支持 Javascript 语法获取元素信息,如:

UIATarget.localTarget().logElementTree();

不过这样做实在麻烦,而且要先录制,速度很慢,需要的系统资源很高。

iOS 平台我们倾向使用 Accessibility Inspector,它是 OSX 提供的无障碍系列工具,更多关于 Authoring Tool Accessibility Guidelines。

通过如下方式启动 Accessibility Inspector:

Xcode -> Open Developer Tool -> Accessibility Inspector

这里是 Accessibility Inspector 的配置文件:

~/Library/Preferences/com.apple.AccessibilityInspector.plist

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

可以看到已经找到了元素名 AXTextField,对应的控件就是 UIATextField,所以我们的登陆功能可以这么封装:

module.exports = function(username, password) {
return this
.waitForElementByXPath('//UIATextField[1]')
.sendKeys(username)
.waitForElementByXPath('//UIASecureTextField[1]')
.sendKeys(password)
.sleep(1000)
.sendKeys('\n')
.waitForElementByName('Login')
.click()
.sleep(5000);
};

我通过反射的方式,把 iOS 的 runtime 类关系都打印并绘制了出来,可以通过继承树图来更多的熟悉 iOS 中的 UI 控件类。

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

iOS 中的 Webview

Macaca 对 iOS 中的 Webview 实现依赖于 ios_webkit_debug_proxy,当你在运行 Macaca 或者通过单步调试方式调试用例的时候,访问 9222 端口即可看到当前打开了哪些网页,调试请通过 Inpsector,这里不赘述。

相关文章:Macaca 测试用例-单步调试

[基于 Node.js 的自动化测试-Macaca] - 如何查找界面元素 [终极篇]

下一篇 – macaca-electron 模块的独立使用

* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除