自动化遍历-appcrawler

下载appclawler

下载地址:https://pan.baidu.com/s/1dE0JDCH#list/path=%2F

查看帮助文档:

java -jar appcrawler-2.4.0-jar-with-dependencies.jar

Usage: appcrawler [options]

  -a, --app <value>        Android或者iOS的文件地址, 可以是网络地址, 赋值给appium的app选项
  -e, --encoding <value>   set encoding, such as UTF-8 GBK
  -c, --conf <value>       配置文件地址
  -p, --platform <value>   平台类型android或者ios, 默认会根据app后缀名自动判断
  -t, --maxTime <value>    最大运行时间. 单位为秒. 超过此值会退出. 默认最长运行3个小时
  -u, --appium <value>     appium的url地址
  -o, --output <value>     遍历结果的保存目录. 里面会存放遍历生成的截图, 思维导图和日志
  --capability k1=v1,k2=v2...
                           appium capability选项, 这个参数会覆盖-c指定的配置模板参数, 用于在模板配置之上的参数微调
  -r, --report <value>     输出html和xml报告
  --template <value>       输出代码模板
  --master <value>         master的diff.yml文件地址
  --candidate <value>      candidate环境的diff.yml文件
  --diff                   执行diff对比
  -vv, --verbose           是否展示更多debug信息
  --demo                   生成demo配置文件学习使用方法
  --help
示例
appcrawler -a xueqiu.apk
appcrawler -a xueqiu.apk --capability noReset=true
appcrawler -c conf/xueqiu.json -p android -o result/
appcrawler -c xueqiu.json --capability udid=[你的udid] -a Snowball.app
appcrawler -c xueqiu.json -a Snowball.app -u 4730
appcrawler -c xueqiu.json -a Snowball.app -u http://127.0.0.1:4730/wd/hub

生成demo.yaml例子

java -jar appcrawler-2.4.0-jar-with-dependencies.jar --demo

---
pluginList: []
saveScreen: true
reportTitle: "雪球自动化遍历"
resultDir: "./test"
waitLoading: 500
waitLaunch: 6000
showCancel: true
maxTime: 10800
maxDepth: 10
capability:
  noReset: "true"
  fullReset: "false"
  appium: "http://127.0.0.1:4723/wd/hub"
  appPackage: com.xueqiu.android
  appActivity: .view.WelcomeActivityAlias 
testcase:
  name: "TesterHome AppCrawler"
  steps:
  - xpath: 行情
    action: click
  - xpath: 龙虎榜
    action: click
    then: 
    - //*[contains(@text,'沪深上榜个股')]
selectedList:
- xpath: "//*[contains(name(), 'Text') and @clickable='true' and string-length(@text)<10]"
- xpath: "//*[@clickable='true']/*[contains(name(), 'Text') and string-length(@text)<10]"

firstList: []
lastList:
- xpath: "//*[@selected='true']/..//*" 
- xpath: "//*[@selected='true']/../..//*"
 
backButton:
- given: []
  when: null
  then: []
  xpath: "Navigate up"
  action: null
  actions: []
  times: 0
triggerActions:
- xpath: 净买入
  times: 10
- xpath: 总成交
  times: 10
- given: []
  when: null
  then: []
  xpath: "share_comment_guide_btn"
  action: null
  actions: []
  times: 0
xpathAttributes:
- "name"
- "label"
- "value"
- "resource-id"
- "content-desc"
- "instance"
- "text"
sortByAttribute:
- "depth"
- "list"
- "selected"
findBy: "default"
defineUrl: []
baseUrl: []
appWhiteList: []
urlBlackList: []
urlWhiteList: []
blackList:
- given: []
  when: null
  then: []
  xpath: ".*[0-9]{2}.*"
  action: null
  actions: []
  times: 0
beforeRestart: []
beforeElement:
- given: []
  when: null
  then: []
  xpath: "/*"
  action: "Thread.sleep(500)"
  actions: []
  times: 0
afterElement: []
afterPage: []
afterPageMax: 2
tagLimitMax: 2
tagLimit:
- given: []
  when: null
  then: []
  xpath: "确定"
  action: null
  actions: []
  times: 1000
- given: []
  when: null
  then: []
  xpath: "取消"
  action: null
  actions: []
  times: 1000
- given: []
  when: null
  then: []
  xpath: "share_comment_guide_btn_name"
  action: null
  actions: []
  times: 1000
assertGlobal: []

启动已经安装过的app

java -jar appcrawler-2.4.0-jar-with-dependencies.jar --capability "appPackage=com.xueqiu.android,appActivity=.view.WelcomeActivityAlias"

定位模式xpath

xpath:
    //*[@resource-id=‘xxxx’]
    //*[contains(@text, ‘密码’)]
正则:
    ^确定$
    ^.*输入密码
包含:
    请
    输入
    密码

自动化遍历支持

    selectedList:需要被遍历的元素范围 
    firstList:优先被点击
    lastList:最后被点击
    tagLimitMax:同祖先(同类型)的元素最多点击多少
    backButton:当所有元素都被点击后默认后退控件定位 
    blackList:黑名单
    maxDepth: 6 遍历的最大深度

触发器

triggerActions:
    需要特定次数的触发动作
    通常用于处理弹框
    xpath: 指定具体按钮 
    action:动作 
    times:规则的使用次数

动作支持action

    “” 只是截图记录
    back 后退
    backApp 回退到当前的app 默认等价于back行为 可定制 monkey 随机事件
    xxx() 执行代码
        Thread.sleep(3000)
        driver.swipe(0.9, 0.5, 0.1, 0.5) click
    longTap
    非以上所有行为是输入 xx ddd

与传统WebDriver的不同点

WebDriver:
    根据id class xpath进行定位
    直接截图 
AppCrawler:ReactWebDriver模式:
    先getPageSource获取所有的元素列表 
    根据配置好的宽泛的xpath直接选择元素 
    然后再生成匹配到的每个元素的唯一定位xpath表达式或者id定位表达式 
    然后调用appium定位并执行action
    截图时增加对选择控件的高亮区分
 

配置文件参考

https://github.com/seveniruby/AppCrawler/blob/2.3.1/src/main/scala/com/testerhome/appcrawler/CrawlerConf.scala

package com.testerhome.appcrawler

import java.io.File

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.openqa.selenium.interactions.Actions

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source

/**
  * Created by seveniruby on 16/1/6.
  */
class CrawlerConf {
  /** 插件列表,暂时禁用,太高级了,很多人不会用 */
  var pluginList = List[String]()
  /** 是否截图 */
  var saveScreen = true
  var reportTitle = ""
  /** 结果目录 */
  var resultDir = ""
  /**在执行action后等待多少毫秒进行刷新*/
  var waitLoading=500
  var waitLaunch=6000
  //var tagLimit=scala.collection.mutable.Map[String, Int]()
  var showCancel = true
  /** 最大运行时间 */
  var maxTime = 3600 * 3
  /** 默认的最大深度10, 结合baseUrl可很好的控制遍历的范围 */
  var maxDepth = 10
  /** sikuli的数据 */
  //var sikuliImages=""
  //todo: 通过数据驱动,支持多设备
  /** appium的capability通用配置 */
  var capability = Map[String, Any](
    //默认不清空数据,防止有人用于微信和qq
    "noReset" -> "true",
    "fullReset" -> "false",
  )

  //测试用例
  var testcase = ReactTestCase(
    name = "TesterHome AppCrawler",
    steps = List[Step](
      Step(xpath = "/*", action = "Thread.sleep(5000)")
    )
  )

  /** 默认遍历列表,xpath有用,action暂时没启用*/
  var selectedList = ListBuffer[Step](
    Step(xpath="//*[contains(name(), 'Button')]"),
    //android专属
    Step(xpath="//*[contains(name(), 'Text') and @clickable='true' and string-length(@text)<10]"),
    Step(xpath="//*[@clickable='true']/*[contains(name(), 'Text') and string-length(@text)<10]"),
    Step(xpath="//*[contains(name(), 'Image') and @clickable='true']"),
    Step(xpath="//*[@clickable='true']/*[contains(name(), 'Image')]"),
    //ios专属
    Step(xpath="//*[contains(name(), 'Image') and @name!='']"),
    Step(xpath="//*[contains(name(), 'Text') and @name!='' and string-length(@label)<10]"),
  )
  /** 优先遍历元素 */
  var firstList = ListBuffer[Step](
  )
  /** 最后遍历列表 */
  var lastList = ListBuffer[Step](
    Step(xpath="//*[@selected='true']/..//*"),
    Step(xpath="//*[@selected='true']/../..//*")
  )
  /** 后退按钮标记, 主要用于iOS, xpath */
  var backButton = ListBuffer[Step](
    Step(xpath="Navigate up")
  )

  //todo: 去掉triggerAction
  /** 引导规则. name, value, times三个元素组成 */
  var triggerActions = ListBuffer[Step](
    Step(xpath="share_comment_guide_btn")
  )

  //自动生成的xpath表达式里可以包含的匹配属
  var xpathAttributes = List("name", "label", "value", "resource-id", "content-desc", "instance", "text")
  /** 先按照深度depth排序,再按照list排序,最后按照selected排序。后排序是优先级别最高的 */
  var sortByAttribute = List("depth", "list", "selected")
  //可选 default|android|id|xpath,默认状态会自动判断是否使用android定位或者ios定位
  var findBy="default"
  /** 用来确定url的元素定位xpath 他的text会被取出当作url因素 */
  var defineUrl = List[String]()
  /** 设置一个起始url和maxDepth, 用来在遍历时候指定初始状态和遍历深度 */
  var baseUrl = List[String]()
  var appWhiteList = ListBuffer[String]()
  /** url黑名单.用于排除某些页面 */
  var urlBlackList = ListBuffer[String]()
  var urlWhiteList = ListBuffer[String]()
  /** 黑名单列表 matches风格, 默认排除内容是2个数字以上的控件. */
  var blackList = ListBuffer[Step](
    Step(xpath=".*[0-9]{2}.*")
  )

  //在重启session之前做的事情
  var beforeRestart=ListBuffer[String]()
  //在执行action之前和之后默认执行的动作,比如等待
  var beforeElement = ListBuffer[Step](
    Step(xpath="/*", action="Thread.sleep(500)")
  )
  var afterElement = ListBuffer[Step]()
  /**是否需要刷新或者滑动*/
  var afterPage = ListBuffer[Step]()
  //afterPage执行多少次后才不执行,比如连续滑动2次都没新元素即取消
  var afterPageMax=2
  //相似控件最多点击几次
  var tagLimitMax = 2
  //个别控件可例外
  var tagLimit = ListBuffer[Step](
    //特殊的按钮,可以一直被遍历
    Step(xpath = "确定", times = 1000),
    Step(xpath = "取消", times = 1000),
    Step(xpath = "share_comment_guide_btn_name", times=1000)
  )
  //只需要写given与then即可
  var assertGlobal = List[Step]()


  def loadByJson4s(file: String): Option[this.type] = {
    if (new java.io.File(file).exists()) {
      println(s"load config from ${file}")
      println(Source.fromFile(file).mkString)
      Some(TData.fromYaml[this.type](Source.fromFile(file).mkString))
    } else {
      println(s"conf file ${file} no exist ")
      None
    }
  }

  def save(path: String): Unit = {

    /*
        //这个方法不能正确的存储utf8编码的文字
        implicit val formats = DefaultFormats+ FieldSerializer[this.type]()
        val file = new java.io.File(path)
        val bw = new BufferedWriter(new FileWriter(file))
        log.trace(writePretty(this))
        log.trace(write(this))
        bw.write(writePretty(this))
        bw.close()
        */

    val file = new java.io.File(path)
    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)
    mapper.writerWithDefaultPrettyPrinter().writeValue(file, this)
    println(mapper.writeValueAsString(this))
  }

  def toJson(): String = {
    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)
    mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this)

  }

  def toYaml(): String = {
    val mapper = new ObjectMapper(new YAMLFactory())
    mapper.registerModule(DefaultScalaModule)
    mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this)
  }

  def loadYaml(fileName: File): CrawlerConf = {
    val mapper = new ObjectMapper(new YAMLFactory())
    mapper.registerModule(DefaultScalaModule)
    mapper.readValue(fileName, classOf[CrawlerConf])
  }

  def loadYaml(content: String): Unit = {
    val mapper = new ObjectMapper(new YAMLFactory())
    mapper.registerModule(DefaultScalaModule)
    mapper.readValue(content, classOf[CrawlerConf])
  }


  def load(file: String): CrawlerConf = {
    load(new File(file)).get
  }

  //如果没有显式配置参数,那么就会用默认值代替
  def load(file: File): Option[CrawlerConf] = {
    val content = Source.fromFile(file, "UTF-8").getLines().mkString("\n")
    file.getName match {
      case json if json.endsWith(".json") => {
        Some(TData.fromJson[CrawlerConf](content))
      }
      case yaml if yaml.endsWith(".yml") || yaml.endsWith(".yaml") => {
        Some(TData.fromYaml[CrawlerConf](content))
      }
      case path => {
        println(s"${path} not support")
        None
      }
    }
  }



posted @ 2019-09-08 21:54  Beyond8  阅读(1164)  评论(0编辑  收藏  举报