新版本已经更新到 1.5.0

https://testerhome.com/topics/5718
结贴

工具在筹备 1.4.0 版本的发布. 所以暂不回答大家各种重复的问题了. 我创建了一个 qq 交流群: 177933995 可自行加入

第四届线下沙龙回顾

3 月份高鹏 徐世钊 mingway 在阿里巴巴举办了第四届 TesterHome 线下沙龙
我在沙龙中分享了 AppCrawler 自动遍历工具的前世今生和一些 feature.
当时因为在完善一些细节. 所以没有放出来试用版本. 今天算是给大家补上吧.

ChangeLog

增加了一个实验性的自动化测试框架. 放出来也是想让大家当小白鼠
增加了自动化测试的支持. 可以在自动化中进入自动遍历.
增加了 Log 插件. 支持读取 Android 的 Logcat 和 iOS 的 syslog 数据.
增加了 TagLimit 插件. 用于自动识别类似的控件减少遍历, 加快速度.
使用了标准的 log4j 风格的 Log 输出
增加了启动的默认划屏操作

下载地址

我把工具打包成了 jar 包.

百度网盘: http://pan.baidu.com/s/1pLcjBkB
新浪微盘: http://vdisk.weibo.com/s/aii55uMrHR9DZ

开源时间还不确定. 心急的同学可以自行反编译. :)

使用介绍

首先安装 appium 并启动

#安装appium
npm install -g appium
#启动appium  Android测试默认连接4730端口的appium. iOS使用的是默认的4723
#如果是Android
appium -p 4730
#如果是iOS测试
appium

以雪球的 app 为例. 雪球是著名的炒股和购买基金的产品, 里面的细分功能会比较多. 你可以借助它了解下自动遍历的大概过程.
首先下载雪球的 app http://xqfile.imedao.com/android-release/xueqiu_761_04122000.apk
因为雪球 app 的默认 LauncherActivity 有点特殊. Appium 识别不出来. 所以得手工加上一个 capability 的参数.
如果是其他的 app, 就不需要了. 执行命令如下

java -jar appcrawler-1.2.1.jar -a xueqiu.apk  -o  demo/ --capability appActivity=.view.WelcomeActivityAlias

当然也有个一键下载并执行的方法, 只是这样下载 app 会比较慢. 只有内网做持续集成的时候才适合.

java -jar appcrawler-1.2.1.jar -a  http://xqfile.imedao.com/android-release/xueqiu_761_04122000.apk  -o  demo/ --capability appActivity=.view.WelcomeActivityAlias

然后就可以出去喝茶了. 回来之后你就可以在 demo 目录下看结果了. 目前的速度是 Android 一小时一千张. iOS 一小时三百张截图的样子.

查看完整帮助

java -jar target/scala-2.11/appcrawler-1.2.1.jar

AppCrawler 1.2.0
app爬虫, 用于自动遍历测试. 支持Android和iOS, 支持真机和模拟器
灵感来源: 晓光 泉龙 杨榕 雪球测试团队出品
移动测试技术交流 https://testerhome.com

Usage: appcrawler [sbt] [options] <args>...

  -a <value> | --app <value>
        Android或者iOS的文件地址, 可以是网络地址, 赋值给appium的app选项
  -c <value> | --conf <value>
        配置文件地址
  -p <value> | --platform <value>
        平台类型android或者ios
  -t <value> | --maxTime <value>
        最大运行时间. 单位为秒. 超过此值会退出. 默认最长运行3个小时
  -u <value> | --appium <value>
        appium的url地址
  -o <value> | --output <value>
        遍历结果的保存目录. 里面会存放遍历生成的截图, 思维导图和日志
  --capability k1=v1,k2=v2...
        appium capability选项, 这个参数会覆盖-c指定的配置模板参数, 用于在模板配置之上的参数微调
  -v | --verbose
        是否展示更多debug信息
  --help

示例
appcrawler -a xueqiu.apk
appcrawler -a xueqiu.apk --capability noReset=true
appcrawler -c conf/xueqiu.json
appcrawler -c xueqiu.json  -p ios --capability udid=[你的udid] -a Snowball.app
appcrawler -c xueqiu.json  -p ios -a Snowball.app -u http://127.0.0.1:4730/wd/hub

Command: sbt [<sbt params>...]
sbt是一个调用sbt命令运行测试的开关. 可以传递sbt的参数

  <sbt params>...
        sbt的参数列表

详细文档介绍

一份未完成的文档
百度网盘: http://pan.baidu.com/s/1c8yjgi
新浪微盘: http://vdisk.weibo.com/s/aii55uMrHRamh

新增自动化支持 demo

不是重复造轮子. 是 scalatest 自带的一个 selenium 的封装. 我之前在社区发过
我新增了三个关键词来简化测试步骤

常用关键字

see 定位方法
tree 元素访问方法
send 发送文字
click on 点击

see

唯一的元素定位方式.

see 是引用了<阿凡达>电影里面一句台词"I See You". 它的作用是当你看到一个控件, 你应该可以根据看见的东西就可以定位它, 无须借助其他工具或者使用 findElementByXXX 之类的函数.

比如有个 Button, 名字是"登录", 它的 id 是 account, 定位它可以通过如下多种方式的任何一种

如果当前界面中存在了有歧义的控件, 比如其他一个名字为"登录"的输入框. 那么上述定位方法中定位中两个控件的定位方法会失效, 你需要自己调整即可.
关于元素定位你只需要用 see 这个方法即可.

tree

打印当前界面布局结构. 是个格式化的 xml 内容.

如果传入一个参数 则会打印符合你给定关键词或者 xpath 定位的元素列表

这个关键词是为了让你摆脱各类的元素定位工具, 比如 appium inspector, uiautomator 之类的工具.

# 这是对应的控件的所有属性
Map(x -> 0, name -> 注册, path -> /0/0/4, visible -> true, y -> 202, tag -> UIAStaticText, enabled -> true, label -> 注册, dom -> , height -> 44, UIAStaticText -> null, xpath -> //UIAApplication[@name="雪球" and @path="/0"]/UIAWindow[@path="/0/0"]/UIAStaticText[@name="注册" and @path="/0/0/4"], valid -> true, hint -> , loc -> //UIAApplication[@name="雪球" and @path="/0"]/UIAWindow[@path="/0/0"]/UIAStaticText[@name="注册" and @path="/0/0/4"], width -> 187.5, value -> 注册)


用于获取当前dom树里面满足条件的第一个控件的属性. 比如我想获取某个TextView的相关属性. 可用
- tree("action_bar_title")("text") 文本
- tree("action_bar_title")("tag") 类型
- tree("action_bar_title")("selected") 是否选中

### demo例子
验证iOS上的设备的兼容性.  只是一个粗糙的小用例.  不是正规的应用. 正规的应用需要把设备数据和用例拆分开. 我这是为了方便简写了一个可运行的用例.
```scala
class TestAppiumDSL extends AppiumDSL {
  import org.scalatest.prop.TableDrivenPropertyChecks._
  val table = Table(
    ("iPhone 4s", "9.1"),
    ("iPhone 5", "8.1"),
    ("iPhone 5", "9.2"),
    ("iPhone 5s", "9.1"),
    ("iPhone 6", "8.1"),
    ("iPhone 6", "9.2"),
    ("iPhone 6 Plus", "9.1"),
    ("iPhone 6s", "9.1"),
    ("iPhone 6s", "9.2"),
    ("iPad Air", "9.1"),
    ("iPad Air 2", "9.1"),
    ("iPad Pro", "9.1"),
    ("iPad Retina", "8.1"),
    ("iPad Retina", "8.2")
  )
  forAll(table) { (device: String, version: String) => {
    test(s"兼容性测试-${device}-${version}_登录验证iphone", Tag("7.7"), Tag("iOS"), Tag("兼容性测试")) {
      iOS(true)
      config("deviceName", device)
      config("platformVersion", version)
      setCaptureDir("/Users/seveniruby/temp/crawl4")
      appium()
      captureTo(s"${device}-${version}_init.png")
      click on see("手机号")
      send("1560053xxxx")
      click on see("//UIASecureTextField")
      send("password")
      captureTo(s"${device}-${version}_login.png")
      click on see("登 录")
      captureTo(s"${device}-${version}_main.png")
      if(device.matches(".*iPad.*")){
        click on see("//UIAButton[@path=\"/0/0/0/5\"]")
      }else {
        click on see("//UIAButton[@path=\"/0/0/3/5\"]")
      }
      tree("seveniruby")("name") should be equals "seveniruby"
      captureTo(s"${device}-${version}_profile.png")
    }
  }
  }

配置文件模板

{
  "pluginList" : [
    "com.xueqiu.qa.appcrawler.plugin.TagLimitPlugin"
  ],
  "saveScreen" : true,
  "currentDriver" : "android",
  "maxTime" : 10800,
  "resultDir" : "",
  "capability" : {
    "deviceName" : "",
    "platformVersion" : "",
    "platformName" : "",
    "launchTimeout": 120000,
    "newCommandTimeout": 120,
    "autoWebview" : "false",
    "autoLaunch" : "true",
    "noReset" : "false"
  },
  "androidCapability" : {
    "deviceName" : "demo",
    "appPackage" : "com.xueqiu.android",
    "appActivity" : ".view.WelcomeActivityAlias",
    "app" : ""
  },
  "iosCapability" : {
    "deviceName" : "iPhone 6",
    "bundleId" : "",
    "platformVersion" : "9.1",
    "autoAcceptAlerts" : "true",
    "app" : ""
  },
  "defineUrl" : [
    "//*[@resource-id='com.xueqiu.android:id/indicator']//*[@selected='true']",
    "//*[@resource-id='com.xueqiu.android:id/tab_name' and @selected='true']",
    "//UIANavigationBar[1]",
    "//*[contains(@name, '_title')]",
    "//*[contains(@resource-id, 'action_bar_title')]"
  ],
  "baseUrl" : [ ".*MainActivity", ".*SNBHomeView.*" ],
  "maxDepth" : 20,
  "blackUrlList" : [ ".*球友.*", ".*png.*", ".*Talk.*", ".*Chat.*", ".*Safari.*", "WriteStatus.*", "Browser.*", "UserProfile.*", ".*消息.*", "MyselfUser", ".*消息.*", ".*MyselfUser.*", ".*股市直播.*", ".*UserVC.*", ".*正文页.*", "SNBTradeSegment" ],
  "backButton" : [
    "//*[@resource-id='action_back']",
    "//*[@resource-id='android:id/up']",
    "//*[@resource-id='android:id/home']",
    "//*[@resource-id='android:id/action_bar_title']",
    "//*[@name='nav_icon_back']",
    "//*[@name='Back']",
    "//*[@name='返回']",
    "//UIAButton[@name='取消']",
    "//UIAButton[@label='返回']",
    "//UIAButton[@name='关闭']",
    "//UIAButton[@name='首页']" ],
  "firstList" : [
    "//UIACollectionView//*",
    "//UIAPopover//*",
    "//UIAWindow[3]//*[not(ancestor-or-self::UIAStatusBar)]",
    "//UIAWindow[2]//*[not(ancestor-or-self::UIAStatusBar)]",
    "//android.widget.ListView//android.widget.TextView",
    "//android.widget.ListView//android.widget.Button",
    "//UIAWindow[1]//UIATableView//UIATableCell[@name!='']",
    "//UIAWindow[1]//UIAStaticText//UIATableCell[@dom!='' and @name!='']"
  ],
  "selectedList" : [
    "//*[@resource-id!='' and not(contains(name(), 'Layout'))]",
    "//*[@content-desc!='' and not(contains(name(), 'Layout'))]",
    "//android.widget.TextView[@clickable='true']",
    "//android.widget.ImageView[@clickable='true']",
    "//*[contains(name(), 'Text')]",
    "//*[contains(name(), 'Image')]",
    "//*[contains(name(), 'Button')]",
    "//*[not(ancestor-or-self::UIAWebView)]"
  ],
  "lastList" : [
    "//*[contains(@resource-id,'group_header_view')]//android.widget.TextView",
    "//*[contains(@resource-id,'indicator')]//android.widget.TextView"
  ],
  "blackList" : [
    "//*[name()='UIATextField']",
    "//*[contains(name(), 'EditText')]",
    "//UIAStatusBar//*",
    ".*Safari", ".*电话.*", ".*Safari.*", "发布", "action_bar_title", ".*浏览器.*", "message", ".*home", "首页", "消息", "弹幕", "发射", "Photos", "地址", "网址", "发送", "拉黑", "举报", "camera", "Camera", "点评", "nav_icon_home", "评论", "回复", "咨询", "分享.*", "转发.*", "comments", "comment", "stock_item_.*", ".*[0-9]{2}.*", "弹幕", "发送", "保存", "确定", "up", "user_profile_icon", "selectAll", "cut", "copy", "send", "买[0-9]*", "卖[0-9]*", "聊天.*", "拍照.*", "发表.*", "回复.*", "加入.*", "赞助.*", "微博.*", "球友.*", ".*开户.*" ],
  "elementActions" : [ {
    "action" : "click",
    "idOrName" : "//*[@resource-id='com.xueqiu.android:id/button_login']",
    "times" : 1
  }, {
    "action" : "xxxxx",
    "idOrName" : "//*[@resource-id='com.xueqiu.android:id/login_account']",
    "times" : 1
  },{
    "action" : "xxxxxx",
    "idOrName" : "//*[@resource-id='com.xueqiu.android:id/login_password']",
    "times" : 1
  }, {
    "action" : "click",
    "idOrName" : "button_next",
    "times" : 1
  },{
    "action" : "xxxxxx",
    "idOrName" : "//UIAStaticText[contains(@name, '登录')]",
    "times" : 1
  },{
    "action" : "xxxxxx",
    "idOrName" : "//UIATextField[contains(@value, '手机')]",
    "times" : 1
  }, {
    "action" : "xxxx",
    "idOrName" : "//UIASecureTextField",
    "times" : 1
  },{
    "action" : "click",
    "idOrName" : "//UIAButton[contains(@name, '登 录')]",
    "times" : 1
  }, {
    "action" : "click",
    "idOrName" : ".*立即登录",
    "times" : 2
  }, {
    "action" : "click",
    "idOrName" : "//*[@name='登 录']",
    "times" : 2
  }, {
    "action" : "click",
    "idOrName" : "//*[@name='登录']",
    "times" : 2
  }, {
    "action" : "scroll left",
    "idOrName" : "专题",
    "times" : 1
  },{
    "action" : "click",
    "idOrName" : "点此.*",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "不保存",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "确定",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "关闭",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "取消",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "稍后再说",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "Cancel",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "这里可以.*",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : ".*搬到这里.*",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "我要退出",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "tip_click_position",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "common guide icon ok",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "icon quotationinformation day",
    "times" : 1
  }, {
    "action" : "click",
    "idOrName" : "icon stock close",
    "times" : 0
  }, {
    "action" : "click",
    "idOrName" : "隐藏键盘",
    "times" : 0
  }],
  "startupActions" : [ ]
}

后记

很多人都在反馈相同的问题, 我工作很忙, 所以就结贴了.
列举下大家反馈的问题. 然后等新版本吧.

各位看官如果阅读的赏心悦目的话, 记得点击帖子下方的"打赏"按钮给打个赏.

老帖介绍: https://testerhome.com/topics/4151


↙↙↙阅读原文可查看相关链接,并与作者交流