https://testerhome.com/topics/5718
结贴
工具在筹备 1.4.0 版本的发布. 所以暂不回答大家各种重复的问题了. 我创建了一个 qq 交流群: 177933995 可自行加入
3 月份高鹏 徐世钊 mingway 在阿里巴巴举办了第四届 TesterHome 线下沙龙
我在沙龙中分享了 AppCrawler 自动遍历工具的前世今生和一些 feature.
当时因为在完善一些细节. 所以没有放出来试用版本. 今天算是给大家补上吧.
增加了一个实验性的自动化测试框架. 放出来也是想让大家当小白鼠
增加了自动化测试的支持. 可以在自动化中进入自动遍历.
增加了 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
不是重复造轮子. 是 scalatest 自带的一个 selenium 的封装. 我之前在社区发过
我新增了三个关键词来简化测试步骤
see 定位方法
tree 元素访问方法
send 发送文字
click on 点击
唯一的元素定位方式.
see 是引用了<阿凡达>电影里面一句台词"I See You". 它的作用是当你看到一个控件, 你应该可以根据看见的东西就可以定位它, 无须借助其他工具或者使用 findElementByXXX 之类的函数.
比如有个 Button, 名字是"登录", 它的 id 是 account, 定位它可以通过如下多种方式的任何一种
如果当前界面中存在了有歧义的控件, 比如其他一个名字为"登录"的输入框. 那么上述定位方法中定位中两个控件的定位方法会失效, 你需要自己调整即可.
关于元素定位你只需要用 see 这个方法即可.
打印当前界面布局结构. 是个格式化的 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" : [ ]
}
很多人都在反馈相同的问题, 我工作很忙, 所以就结贴了.
列举下大家反馈的问题. 然后等新版本吧.
各位看官如果阅读的赏心悦目的话, 记得点击帖子下方的"打赏"按钮给打个赏.