Macaca 基于 Macaca 的混合 H5 应用 UI 自动化入门

Yinxl · 2017年08月08日 · 最后由 中元 回复于 2019年06月05日 · 5174 次阅读

基于 Macaca 的混合 H5 应用 UI 自动化入门

混合 H5 应用 UI 自动化是移动应用自动化中无法绕过的一节,作为 H5 应用,自动化的方式与 Native 略有不同,主要体现在元素的定位以及操作,以及上下文的切换等,本文针对 H5 的自动化入门分享一些基础知识。

H5 应用如何查找元素

在 Native 的 UI 自动化中,我们通过 app-inspector 查找 UI 元素,但是这并不适用于 H5 应用 (在 app-inspector 中,webview 会被识别为一整块 view,看不到子 view),那么针对 H5 应用应该如何定位呢?

针对 H5 应用,我们需要用 H5 的调试方式来查看页面元素,针对 iOS 和安卓平台有不同的查看方式,但因为 H5 代码是一份,所以不管我们用哪个工具看,最终得到的结果是一样的。

Android 定位 H5 元素

以 Android 为例,我们需要使用 chrome:inspect 方法,使用此方法有以下几个前提:

  1. 安卓设备打开开发者模式
  2. chrome 浏览器需要登录
  3. 要 inspect 的 webview 是支持 debug 模式的 (除了定制过的内核,一般都是支持的)

保证了以上几个前提下,我们就可以用 inspect 工具来查看元素了,使用方式非常简单,首先在设备上打开要 inspect 的 webview,然后打开 chrome 浏览器输入 chrome://inspect 就可以看到要 inspect 的页面了:

如上图,点击 inspect 就可以看到对应页面的元素结构了:

通过这样,我们就可以找到定位一个 H5 元素的标识了,与 Native 不同的是 H5 元素除了可以通过 class,id 定位外,还可以通过 css 等 H5 特有的定位方式进行定位,具体的可以参考 API 文档,在 H5 中我们常用的定位方式为 CSS 样式,具体的值可以通过如下方式获得:

css

比如如上我们 copy 到的值为 “#page-bd > section.user-profile > div.user-login.clearfix > a”
则在查找时可以通过如下脚本:

driver.elementByCss("#page-bd > section.user-profile > div.user-login.clearfix > a");

iOS 定位 H5 元素

同安卓类似,不过 inpect 通过 Safari 浏览器进行。
具体操作步骤可参考:
http://www.saitjr.com/ios/ios-user-safari-debug-webview.html

H5 应用如何开始自动化

H5 应用的自动化脚本写法与 Native 的基本一致,上面我们讲了如何定位元素,定位元素后剩下的操作就与 Native 一致了,不过有一点要声明的是要对 H5 应用进行 UI 自动化,首先要切换 Contexts 到 H5 的 contexts(因为混合应用中会存在两个上下文,只有切换到 H5 的上下文之后相关的操作才能生效)

切换上下文的方法在我们自己封装的 biz 层中已经进行了封装 (关于 biz 层的使用,参考:UI 自动化 Macaca-Java 版实践心得),使用 biz 层,只需要在进入 H5 页面后,开始 H5 自动化之前执行如下命令即可

driver.switchFromNativeToWebView();

如果使用原生的 macaca client,可以参考 biz 层 switchFromNativeToWebView 中的写法自行处理,具体处理如下:

JSONArray contexts = driver.contexts();
driver.context(contexts.get(contexts.size() - 1).toString());

完成上下文切换之后就可以愉快的进行 H5 的自动化了 ,其他用法与 Native 基本一致。

常见问题

  1. switchFromNativeToWebview 报错,这里出问题绝大部分是因为 chrome 版本与 chromedriver 版本不匹配导致的。 chrome 版本与 chromedriver 版本需要一一对应

此处的 chrome 版本是指 app 内部指定的 webview 的内核版本,在我们通过 chrome:inspect 查看页面元素的时候可以看到这个版本,如下:
img

chromedriver 在 macaca-android 驱动内,主要是为了进行 webview 的自动化服务的,chromedriver 的版本必须与 app 内部 webview 的版本相匹配,webview 的自动化才能正常进行,因此在进行 h5 自动化前,首先要保证 app 内的 webview 的版本与 macaca-android 内的 chromedriver 的版本是互相匹配的,他们的对应关系可从如下链接查看得到:
https://huilansame.github.io/huilansame.github.io/archivers/chromedriver-to-chrome-version

综合错误提示以及版本对应关系可知,当前我的 chrome 版本为 v55,因此对应的 chromedriver 需要 v2.25,
如要安装正确的 chromedirver 版本,可以在本地环境变量中指定需要的版本,在本地的.bashprofile 或者.zshrc 等中,也就是设置 JAVA_HOME 环境变量的地方,指定 CHROMEDRIVER_VERSION 的版本号,如下:

export CHROMEDRIVER_VERSION=2.25

然后重新安装安卓驱动,就可以安装指定版本的 chromedriver 了

$  cnpm i macaca-android -g

执行如上命令后,会看到新的驱动安装过程中 chromedriver 更新成了 2.25 版本 (如下图),则说明安装成功。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 8 条回复 时间 点赞

Android-chrome 浏览器,如果 H5 页面涉及访问文件 (文件或上传图片之类的),出现系统提示之类的,在 chrome 这个调试里好像不是很好用,请问有什么别的方法可以查看元素么?

如果调用的是系统功能 那可能已经切换到 Native 操作了 这时候可以用 app-inspector 看下能否查看到

Yinxl 混合 H5 应用 UI 自动化进阶 中提及了此贴 11月08日 22:04
Yinxl Macaca-Java 版入门指南 中提及了此贴 05月11日 09:40

求助,pc 桌面版基于 electronjs 的桌面版应用的 UI 页面中,需要使用到鼠标右键的操作,试问鼠标右键的模拟操作 macaca 如何实现呢,api 中未发现,借问求分享哦

我用这样跟你这个差不多的:driver.elementByCss("body > button");

android 5.0.2 版本
报错如下:
2018-12-06 15:41:34 Request:http://localhost:3456/wd/hub/session/2927f853-4288-4112-89c0-13a5aa0f98e9/element:using":"css","value":"body{" > button"}
2018-12-06 15:41:35 Response:{"status":32,"value":"Argument was an invalid selector (e.g. XPath/CSS).","sessionId":"2927f853"}
java.lang.Exception: Argument was an invalid selector (e.g. XPath/CSS).
at macaca.client.common.Utils.handleStatus(Utils.java:162)
at macaca.client.common.Utils.executeRequest(Utils.java:71)
at macaca.client.common.Utils.postRequest(Utils.java:119)
at macaca.client.common.Utils.request(Utils.java:140)
at macaca.client.MacacaClient.findElement(MacacaClient.java:189)
at macaca.client.MacacaClient.elementByCss(MacacaClient.java:262)
at android.service.impl.PhotoRejectionImpl.electronicBusiness(PhotoRejectionImpl.java:200)
at android.service.impl.PhotoRejectionImpl.photoRejectionClick(PhotoRejectionImpl.java:54)
at android.service.impl.TemporaryDepositReceiptImpl.temporaryDepositReceiptClick(TemporaryDepositReceiptImpl.java:29)
at android.test.TemporaryDepositReceiptTest.test_case01(TemporaryDepositReceiptTest.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

应该如何解决呢,大神

在 app 中动态生成的 HTML 页面我用楼主的方法去切换到 webview 上下文,切不进去还是在 NATIVE_APP 下的,我用 driver.context("WEBVIEW");去切上下文它就直接报错:A request to switch to a different window could not be satisfied because the window could not be found.

楼主问一下,我用这种方式元素页面是空的,这个怎么办啊?

中元 回复

网络被 block 了

可是我点击按钮之类的都正常,而且 network 里面接口都 ok 啊

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