Appium appium 自动遍历的参考代码

思寒_seveniruby · 2014年02月26日 · 最后由 xi 回复于 2016年12月22日 · 3172 次阅读
本帖已被设为精华帖!

背景

上次支付宝沙龙分享了基于 appium 的自动遍历小工具, 有些朋友感兴趣, 想看下代码,
为了给大家一个参考, 我这里贴部分的关键算法代码给大家吧
这份代码写的很挫, 功能也很简单, 只是个演示 demo, 是为了证明 appium can do IT.
我计划用 Java 重构, 这个只当是参考吧

部分代码参考

def capabilities
    {
        'browserName' => '',
        'device' => 'android',
        'version' => '4.1',
        'app-activity'=> 'com.taobao.tao.Welcome',
        'app-package'=> 'com.taobao.taobao'
        #'app-activity'=> '.activity.MainActivity',
        #'app-package'=> 'com.sankuai.meituan'
    }
end

def setup
    server_url = 'http://127.0.0.1:4723/wd/hub'
    @driver = Selenium::WebDriver.for(:remote, :desired_capabilities => capabilities, :url => server_url)
    @driver.manage.timeouts.implicit_wait = 30 # seconds
    puts 'sleep 20'
    sleep 20
    @driver
end
def config()
    @config={}
    @config['blacklist']=['立即抢购', '美团承诺 团购无忧', '退款']
end

def refresh()
    root=[]
    el_array=@driver.find_elements(:xpath, "//text[@clickable=true]")
    #如果点击没有发生变化, 就跳过
    if el_array.size==@list.size
        return @list
    end
    el_array.each do |node|
        begin
            text=node.text
            location=node.location
            is_skip=false
            next if text.strip==''
            next if text.size<2
            @config['blacklist'].each do |keyword|
                if text.index(keyword)
                    is_skip=true
                    break
                end
            end
            next if is_skip
            current={}
            current['click']=false
            #位置相差不大 也认为是相同
            current['sign']=text+'|'+location.x.to_s[0..1]
            current['text']=text
            current['object']=node
            root << current
        rescue Exception=>e
            puts e.message
        end
    end
    root
end
def find_return_root(el)
    current=nil
    @nodes.each do |node|
        if node.content['sign']==el['sign']
            current=node
            break
        end
    end
    current=current.parent if current
    return(current)
end
def travel()
    @list||=[]
    @nodes ||= Tree::TreeNode.new("ROOT", {})
    @current||=@nodes
    @index||=0
    @index+=1
    @list=refresh()
    has_new=false
    return_root=nil
    @list.each do |el|
        #判断是否曾经出现过
        return_root=find_return_root(el)
        if return_root==nil
            has_new=true
            #如果是新元素, 就添加到tree中
            @current << Tree::TreeNode.new(el['sign'], el)
        end
    end
    #如果没有新元素, 代表回到某个父节点
    if has_new==false
        @current=return_root
    end
    save()
    if @current.level>7
        @driver.navigate.back
        travel()
    end

    #从未被点击的地方点击
    current=nil
    @current.children.each do |child|
        if child.content['click']==false
            current=child   
            @list.each do |node|
                if node['sign']==current.content['sign']
                    current.content['click']=true
                    @current=current
                    node['object'].click
                    sleep 3
                    travel()
                    break
                end
            end
            break               
        end
    end
    if current==nil
        @driver.navigate.back
        travel()
    end
end

运行结果

因为这次采用的是 uiautomator, 代码也优化, 所以执行时间很长
跑十几分钟, 手机就因为锁屏失败, 所以针对淘宝 app, 我一直没跑全过, 生成的思维导图只是淘宝部分栏目的浅层结构图 想跑全的话, 就自己修改手机, 或者用虚拟机吧
如下是生成的结果:

计划

我接下来, 我在做上次分享的 ppt 最后一页的事情, 我想基于这个搞个在线服务. 有实力感兴趣的同学可以报名参加.
主要是为第三方的企业提供 app 遍历和附加服务. 一个实验性的技术项目.
目标

  1. 优化速度
  2. 优化算法, 解决回环
  3. 自动生成 appium 的自动化代码
  4. 支持规则
  5. 从 ruby 转到 java 上去实现
  6. 做个服务网站
  7. 支持 dom diff 和灰度对比

捐助论坛

论坛服务器有成本, 我们希望为 TesterHome 挣点云服务器费用.
捐助论坛超过 100 元的同学或论坛精华帖超过 10 个的同学可得到全部的代码, ppt 和演示用例
论坛创始人是@lihuazhang 恒温同学, 真名张立华.
我们的支付宝官方账号: testerhome@126.com
支付宝收款地址: https://me.alipay.com/testerhome

TesterHome支付宝

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

@driver.find_elements(:xpath, "//text[@clickable=true]") 这句是重点

感谢思寒。 我们论坛运行在美团上快三个月了。 美团提供的三个月赞助也结束了。之后,我们会自己运营,尽力为大家提供非常稳定的网站服务。

我才 2 个精华帖,我要努力了,期待 Java 版本。

#1 楼 @qddegtya 明显这句是最基本的,解决回环是重点

#1 楼 @qddegtya @vigossjjj 重要的是算法,点击这个没什么, 点击是入口, 现在只是纯点击, 以后加了规则, 就可以支持更多操作了, 比如滑动, 自动输入. 目前还得加上回环算法才算靠谱 所以除了怕嘲笑, 我也觉得没什么可保留的 , 就贴出来给大家参考下.

大家有好的思路, 也不放 show 下, 欢迎指教

另外, 这种纯黑盒的方式也不太好, 通过插桩可以获得更多的建模数据. 也是个缺点, 还需要继续想办法. 我倒是挺喜欢百度 Cafe 的做法.使用 hook 来解决

#6 楼 @seveniruby 之前写了基于 robotium 的遍历 半成品。。。 思想就是树结构 叶子 节点,不过我觉得效率好低 后来就没再搞。不过前几天刚搞了个 android 的兼容性遍历,原理就是将 intent 序列化,保存成 2 进制文件,使用的时候读到内存中就好,直接跳进相应 activity,截图保存。

#7 楼 @vigossjjj 看来我抛砖引玉的思路达到了, 你这个做法很飘逸哦. 不过在没有上下文的情况, 直接跳进某些 activity 会不会有问题? 比如缺少对应的数据导致里面的展现出现差异.

#8 楼 @seveniruby 一般不会 每一版都会序列化一次所有 intent,并且这个工作是在 RD 合并到 Release 分支后进行的,RD 也会协助 QA 进行。目前来说这种测试只需要关注 UI 展示就好了。

#8 楼 @seveniruby 大概 5 分钟就可以把所有 activity 遍历完成。。。

#9 楼 @vigossjjj 一些交互如何解决那, 如果用户输入的验证, 或者对于一些需要联网展现的 ListView?

#11 楼 @seveniruby intent 只是去跳转 activiy 至于展示的数据 如 webview 里面的数据,是不受 intent 控制的,当然 用户验证的化 可以基于脚本去处理,忘记说了 每次遍历前都需要跑一个 precondition,比如登录,直接跳到登录界面进行。

#11 楼 @seveniruby intent 只是告诉程序往哪里去跳 至于数据展示 由内部逻辑自己控制,listview 也一样

加入点随机功能:随机元素,随机事件,做到彷 monkey 又达到一定程度的可控。

#12 楼 @vigossjjj 有些情况是必须前面输入一个查询词语 后面才能查询 不过这种通过 intent 也可以做到 我关注黑盒 黑盒貌似也可以通过 am 实现界面调用 只不过传参数方面就不行了 因为黑盒不会知道具体的参数 除非通过底层拦截

#14 楼 @skwinleo 嗯 可以做个随机规则 不过我感觉价值不大 除了稳定性也测试不出来什么有价值的问题了

#1 楼 @qddegtya 其实这句是有 bug 的 我这样写是为了大家看懂 目前这样查询会得到所有文本 而不是可点击的 真实的脚本里是个复杂的查询条件

#15 楼 @seveniruby 有时间我把代码贴出来一起深入讨论哈~

#7 楼 @vigossjjj 这个方法只要 activity 能正确启动起来,那对于提高遍历覆盖率和加快遍历速度很有帮助呀,之前发现直接启动有些 activity 起不来,一直没做,后续尝试一下,随机遍历结合这种方式一块做效果应该会不错。

这两天正在看 monkeyrunner,看完后开始学习 appium

#10 楼 @vigossjjj 士多啤梨亚是吧~~?看头像很眼熟阿~不过话说你将 intent 序列化当时不是说要 apk develop 的过程中将其序列化的么?是后期测试这边可以将其序列化,然后 am start 就都能够起来了?

#21 楼 @monkey 最后决定将序列化的时机放到 release 比较稳定 一次序列化后 开始遍历测试。如果你想用 am start 启动 activity 就必须可以解释 intent 文件 传参 我的做法是用 instrument 将 intent 打包读到内存中 setIntent 再 startActivity #21 楼 @monkey

通过检查 clickable 来找可点击的元素,这会导致找到很多多余的东西

#23 楼 @spikeshen 是的 这块需要有个复杂的规则来找到真正可独立点击的连接

#25 楼 @seveniruby 我之前也尝试写过对所有可点击元素进行遍历的方法。。。遇到的问题:
第一,如何来过滤同一个实现但重复的可点击元素,我看这里是用元素的像素差异来做的判断,于是就有第二个问题。
第二,对于一些布局上是嵌套的元素,外层和内层都是可点击的,但是两者的实现是不同的,用像素差异来判断就有问题。
第三,某元素点击后是在当前页隐藏或者显示某些元素 (比如呼出新的菜单),这会导致 dom tree 发生变化。

#25 楼 @seveniruby 或者写个方法,用来生成一个 tree,每次点击后去记录点击的元素,和点击后的特征点,然后根据这个去判断是否点击过。。之后每次只要根据这个 tree 的结构去做遍历。不过怎么都很麻烦。

#27 楼 @spikeshen 加一层 isVisable 判断

#28 楼 @vigossjjj 尝试过,还是有问题

#27 楼 @spikeshen 我有这个逻辑, 会判断已经点击过的元素. 已经实现了. @vigossjjj 是否值得点击的元素, 需要有个很全面的规则, 比如上层的 linear 或者 frame 需要可被点击, 内部的元素就算属性是不可点击 其实也是可以点击的.

#30 楼 @seveniruby 直接往上层去找 viewgroup 如果到 root 都为可见 那么必然是可见的 可见性解决了 后边判断是否可点击 这里面包含可点击可跳转的和可点击不可跳转的 下面就是走逻辑判断了 不能再过滤了

#3 楼 @lizhenghuan 开玩笑了 你写还不是分分钟的事情

技术项目搞的咋样了?有多少个志愿者?

#33 楼 @kevin_xu_v 还没正式立项那. 这个项目不是优先的. 我们计划先搞定 appium 的新一轮 pull request, 再完善这个

#34 楼 @seveniruby 哦哦,加油!!!我准备扫描二维码,O(∩_∩) O

好帖,我想问下对于 h5 的页面也支持遍历吗?

#36 楼 @yunmu 这个脚本暂不支持, 不过稍加改造即可. 因为 appium 支持 html5 是依赖 2 个特殊方法. 加上就可以了.

#22 楼 @vigossjjj 用 instrument 将 intent 打包读到内存中 setIntent 再 startActivity
是否方便告知大概要怎么进行

#38 楼 @skwinleo intent 直接序列化到文件中,用的时候直接反序列化读取到内存中,建议自己去覆写序列化与反序列化的方法,因为手机 ROM 的不同,反序列化后的格式会不同,注:序列化与反序列化必须配对,才能正确读取 intent

#39 楼 @vigossjjj 我想到了新的方式, 直接扫描源代码, 找到所有的 intent 调用. 这样会知道要调用数据, 但是不知道传的参数值会是多少, 然后再通过动态的监控来记录 intent 的调用数据.
另外一个方法是通过编译器来推导出 intent 调用.

你的序列化的工作是如何集成到开发的过程中的那,你是如何知道有多少 intent, 并通过什么方式调用序列化的那? 是自动序列化, 还是必须调用某个入口功能?

#40 楼 @seveniruby 你指的动态监控是如何实现的

#41 楼 @skwinleo 动态 hook 关键 api

思维导图什么工具画的呢

#43 楼 @liuqqwwe 根据格式自动生成的. 不过现在我更建议使用 R 来实现这个绘图

#44 楼 @seveniruby 看图的风格像是用 freemind 画的,请问你是遍历树保存为 xml 用 freemind 截图,还是 freemind 有开放相关的 api,

#44 楼 @seveniruby 如果用 R 语言实现,绘制功能都要自己写吧,位置动态调整这些功能工作量也不小吧

#46 楼 @liuqqwwe 给 R 一个 csv 数据即可. 位置和布局 R 早就设计好了, 而且还有很多布局可选

#47 楼 @seveniruby 有 java 重构部份的内容么

楼主,你说 appium 支持 html5 是依赖 2 个特殊方法,可否详细告知?

@ 楼主@seveniruby
全部的代码, ppt 和演示用例
能分享下么 就看看具体实现了 遇到很多麻烦的地方

楼主,你说 appium 支持 html5 是依赖 2 个特殊方法,可否详细告知?

Hi 楼主
1、通过点击的方式,获取当前页面的可点击的按钮,如何能保证它是在当前的 Acitivity 中继续执行?
2、执行的过程中,突然弹出的页面,如何处理?

#47 楼 @seveniruby 请问一下 appium 有什么方法可以获取到看不见的控件,例如 listview 中需要下滑才能看见的 item,如果边下滑边判断如何判断滑到最底部

看来只能在 app 里加判断是否滑到最底部了,不知道还有其他方法没有

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