其他测试框架 Monkeyrunner-EasyMonkeyDevice 走过的坑

owen · 2016年08月31日 · 最后由 owen 回复于 2016年09月01日 · 467 次阅读

Monkeyruner 结缘于分手

Monkeyrunner 写了几个小脚本,发现方便快捷,但是没有兼容性,换个手机就不行了。so,偶然间看到了 DasyMonkeyDevice 这个方法,感觉发现了新大陆似的,因为它可以通过 id 的方式来获取当前页面的 id 然后兼容性问题就解决了。
实际操作确实惨不忍睹,且听我细细讲来。

教程如下:

导入包

from com.android.chimpchat.hierarchyviewer import HierarchyViewerViewNode
#创建devices
device = MonkeyRunner.waitForConnection()
edevice = EasyMonkeyDevice(device)
#操作
easyDevice.touch(By.id('id/btnSignUp'),MonkeyDevice.DOWN_AND_UP)
一些使用方法:
连接设备
#EasyMonkeyDevice(MonkeyDevice device)
device = MonkeyRunner.waitForConnection()
eDevice=EasyMonkeyDevice(device)
触摸点击
#touch(By selector,TouchPressType type)
eDevice.touch(By.id('id/text1'),MonkeyDevice.DOWN_AND_UP)
设置文本信息
#type(By selector, String text)
eDevice.type(By.id(noteId), 'New')
元素是否存在
#exists(By selector)
noteId = 'id/note'
if True == eDevice.exists(By.id(noteId)):
    print 'Note exist'
else:
    print 'Note not found!'
    exit(2)
元素是否可见
#visible(By selector)
if True == eDevice.visible(By.id(noteId)):

    print 'Note is visible'

else:

    print 'Note is invisible'

    exit(3)
获取元素的文本
#getText(By selector)
text = eDevice.getText(By.id(noteId))
获取窗口的 id
#getFocusedWindowId()
winId = eDevice.getFocusedWindowId()
获取当前元素的坐标
#locate(By selector)
locate = eDevice.locate(By.id(noteId))
print 'Location(x,y,w,h) is:',locate

ok,写了一个小 demo 后运行之后报错

cannot import name viewNode

what?多方查证修改如下:
from com.android.hierarchyviewerlib.models import ViewNode
修改完成后不报错了,然后再次运行!
Could not connect to the view server
Unable to get view server version

这个问题如何处理呢?

先了解下 Hiararchy Viewer 这个 bitch,官方资料链接(https://developer.android.com/studio/profile/optimize-ui.html
Hierarchy Viewer 能获得当前手机实时的 UI 信息,给界面设计人员和自动化测试人员带来极大的便利。
在 Android 的官方文档中提到:
To preserve security, Hierarchy Viewer can only connect to devices running a developer version of the Android system.
即:出于安全考虑,Hierarchy Viewer 只能连接 Android 开发版手机或是模拟器 (准确地说,只有 ro.secure 参数等于 0 且 ro.debuggable 等于 1 的 android 系统)。Hierarchy Viewer 在连接手机时,手机上必须启动一个叫 View Server 的客户端与其进行 socket 通信。而在商业手机上,是无法开启 View Server 的,故 Hierarchy Viewer 是无法连接到普通的商业手机。

无语了,能解决么?方法如下:

以下操作有风险------------------->

Android 源码实现这一限制的地方在:
ANDROID 源码根目录\frameworks\base\services\java\com\android\server\wm\WindowManageService.java

检验一台手机是否开启了 View Server 的办法:

adb shell service call window 3
若返回值是:Result: Parcel(00000000 00000000 '........')" 说明 View Server 处于关闭状态
若返回值是:Result: Parcel(00000000 00000001 '........')" 说明 View Server 处于开启状态

若是一台可以打开 View Server 的手机(Android 开发版手机 、模拟器 or 按照本帖步骤给系统打补丁的手机),我们可以使用以下命令打开 View Server:
adb shell service call window 1 i32 4939
使用以下命令关闭 View Server:
adb shell service call window 2 i32 4939

实现步骤:

经过一番调查和实践,我发现其实只要是 root,并且装有 busybox 的手机,通过修改手机上/system/framework 中的某些文件,就可以开启。本文参考了http://blog.apkudo.com/tag/viewserver/Windows,若你是 Linux 的操作系统,直接看原帖吧):,以下是具体步骤(本人基于
前提是:你的手机已经获得 ROOT 权限,且有 BUSYBOX
另外:请仔细阅读本帖的评论,或许你会有新的收获。

1.将商业手机通过 USB 连接 PC,确保 adb 服务运行正常

2.备份手机上/system/framework/中的文件至 PC。备份的时候请确保 PC 上保存备份文件的文件夹结构与手机中的/system/framework 相同

例如:新建 ANDROID_SDK_ROOT\system\framework 文件夹 (本文出现的 ANDROID_SDK_ROOT 指你安装 Android SDK 的根目录)
接着在 cmd 中跳转至 ANDROID_SDK_ROOT\platform-tools 文件夹下,输入以下代码进行备份:
adb pull /system/framework ANDROID_SDK_ROOT\system\framework

3.进入 adb shell,输出 BOOTCLASSPATH:

推荐的做法:

  1. 在 adb shell 中 echo $BOOTCLASSPATH > /sdcard/bootclasspath.txt
  2. 退回到 windows cmd 中,输入 adb pull /sdcard/bootclasspath.txt
  3. bootclasspath.txt 将会保存在 C:\Users\你的用户名 文件夹下 在第十五步中将会用到这个 txt 中的内容。

4.下载 baksmali 和 smali 工具。这两个工具是用来反编译和编译 odex 文件的。

下载地址:
https://dl.dropboxusercontent.com/u/5055823/baksmali-1.4.2.jar
https://dl.dropboxusercontent.com/u/5055823/smali-1.4.2.jar
假设我将这两个 jar 都下载到了 ANDROID SDK 根目录下。

5.运行 baksmali 反编译\system\framework 下的 services.odex 文件:

java -jar ANDROID_SDK_ROOT\baksmali-1.4.2.jar -a 17 -x ANDROID_SDK_ROOT\system\framework\services.odex -d ANDROID_SDK_ROOT\system\framework
参数解释:https://code.google.com/p/smali/wiki/DeodexInstructions
想特别说明的是 “-a” 后跟的数字,表示你系统的 API Level(与你的系统版本有关)。系统版本和 API Level 的对照关系如下:
[图片]

(另外,你不会连 java -jar 都不能运行吧?快去装 jdk!)
此步成功的话,在同文件夹下 (对于我,就是 ANDROID_SDK_ROOT),会有个 out 文件夹生成

这里顺便解释一下 odex 文件和 dex 文件。
dex 文件:Dex 是 Dalvik VM executes 的全称,即 Android Dalvik 执行程序,并非 Java 的字节码而是 Dalvik 字节码,16 进制机器指令。
odex 文件:将 dex 文件依据具体机型而优化,形成的 optimized dex 文件,提高软件运行速度,减少软件运行时对 RAM 的占用。
smali 文件:将 dex 文件变为可读易懂的代码形式,反编译出文件的一般格式。

6.用 Eclipse 打开 out\com\android\server\wm\WindowManagerService.smali 文件

查找.method private isSystemSecure() Z 这个函数

================================================================
.method private isSystemSecure()Z
    .registers 4

    .prologue
    .line 5965
    const-string v0, "1"

    const-string v1, "ro.secure"

    const-string v2, "1"

    invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_22

    const-string v0, "0"

    const-string v1, "ro.debuggable"

    const-string v2, "0"

    invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_22

    const/4 v0, 0x1

    :goto_21
    return v0

    :cond_22
    const/4 v0, 0x0

    goto :goto_21
.end method
================================================================

在这段代码的倒数 7,8 行 “:goto_21” 和 “return v0” 之间加入"const/4 v0, 0x0"一行.这样,就使得 v0 返回的值永远为 0x0,即 false,这样就跳过了 WindowManagerService.java 里对 isSystemSecure 的判断。
.method private isSystemSecure() Z 函数最后变为:

================================================================
.method private isSystemSecure()Z
    .registers 4

    .prologue
    .line 6276
    const-string v0, "1"

    const-string v1, "ro.secure"

    const-string v2, "1"

    invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_22

    const-string v0, "0"

    const-string v1, "ro.debuggable"

    const-string v2, "0"

    invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-eqz v0, :cond_22

    const/4 v0, 0x1

    :goto_21
    const/4 v0, 0x0
    return v0

    :cond_22
    const/4 v0, 0x0

    goto :goto_21
.end method
=====================================================================================

7. 现在运行 smali,重新编译:

java -jar smali-1.4.2.jar -o classes.dex
这时候,应该在 ANDROID_SDK_ROOT 文件夹中出现了 classes.dex 文件

8. 下载 windows 下的 zip 工具:

https://dl.dropboxusercontent.com/u/5055823/zip.exe
假设,我也把 zip.exe 放进了 ANDROID_SDK_ROOT 文件夹

9.确认当前 cmd 命令行运行目录为 ANDROID_SDK_ROOT,运行:

zip.exe services_hacked.jar ./classes.dex
这时候在 ANDROID_SDK_ROOT 文件夹下,出现了打包好的 services_hacked.jar

10.进入 adb shell,输入 su 获得 ROOT 权限

11.重新挂载/system,并更改/system 权限

参考步骤(仅供参考,请确保使用相适应于自己手机的正确方法。请参考下面的"galfordq 的 blog"用户的回复):
a. 输入 mount,查看哪个分区挂载了/system,例如我的是:
[图片]
b. 输入以下命令重新挂载/system,并更改/system 权限(请将 “/dev/block/mmcblk0p25” 替换成你的/system 挂载分区):
mount -o rw,remount -t yaffs2 /dev/block/mmcblk0p25
chmod -R 777 /system 使得/system 可以被我们任意修改

这一步的作用,主要是为了第 17 步能够将/system/framework 里的 services.odex 替换掉。这一步若不成功,在第 17 步的时候可能出现权限不够,无法替换的错误(Read-Only File System)

12.下载 dexopt-wrapper 文件

https://dl.dropboxusercontent.com/u/5055823/dexopt-wrapper
我们也将 dexopt-wrapper 文件放在 ANDROID_SDK_ROOT 文件夹中

13.将 services_hacked.jar 和 dexopt-wrapper 复制到手机的/data/local/tmp 文件夹中

adb push ANDROID_SDK_ROOT/services_hacked.jar /data/local/tmp
adb push ANDROID_SDK_ROOT/dexopt-wrapper /data/local/tmp

14.进入 adb shell,输入 su 后,将 dexopt-wrapper 的权限改为 777

chmod 777 /data/local/tmp/dexopt-wrapper

15.在 adb shell 中 cd 到/data/local/tmp 文件夹下,运行:

./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex <本帖第三步存的地址,但是要删除其中的":/system/framework/services.jar">

这一步就是将第七部生成 dex 文件最终优化成了 odex 文件。

例如我的命令是:./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/
framework.jar:/system/framework/framework2.jar:/system/framework/android.policy.jar:/system/
framework/apache-xml.jar:/system/framework/HTCDev.jar:/system/framework/HTCExtension.jar:/system/
framework/filterfw.jar:/system/framework/com.htc.android.bluetooth.jar:/system/framework/wimax.jar:

/system/framework/usbnet.jar:/system/framework/com.orange.authentication.simcard.jar

这样,便在/data/local/tmp 文件夹中生成了我们自己的 odex:services_hacked.odex
[图片]

16.给我们自己生成的 services_hacked.odex 签名:

busybox dd if=/system/framework/services.odex of=/data/local/tmp/services_hacked.odex bs=1 count=20 skip=52 seek=52 conv=notrunc
参数解释:
if = input file
of = output file
bs = block size (1 byte)
count = number of blocks
skip = input file offset
seek = output file offset
conv=notrunc – don’t truncate the output file.

17.将/system/framework 里的 services.odex 替换成我们自己制作的 services_hacked.odex 吧!

dd if=/data/local/tmp/services_hacked.odex of=/system/framework/services.odex
这一步运行后,过一小会儿 (1 分钟以内) 手机就自动重启了!稍等片刻吧!

18.成功重启后,用以下命令打开 View Server:

adb shell service call window 1 i32 4939
用以下命令查看 View Server 是否打开:
adb shell service call window 3
返回的值若是 Result: Parcel(00000000 00000001 '........'),那么你就起了!

OK,结论如下,坑比一个,还是用 appium 吧!

参考资料:http://maider.blog.sohu.com/255448342.html

共收到 4 条回复 时间 点赞

monkeyrunner 早放弃了,坑逼一个

注意 markdown 格式

恒温 内容不符合版规屏蔽此话题 09月01日 07:49

格式问题

owen #5 · 2016年09月01日 Author

#4 楼 @Lihuazhang 已修改

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