思绪

最近完成了自定义 Appium 的需求,让 Appium 内置了自动识别权限框并点击的能力,参考我的知乎专栏:自定义 Appium 之路

但遇到另外一个问题,就是 testerhome 思寒开发的 AppiumCrawler 并不支持 Appium 最新版,也就是当前的 1.12 版本,只支持到 1.8 版本,让人很是捉急。

本来是想基于 1.8 重新自定义一个 appium,但是发现这个 appium 实在太老了,下载下来编译都有各种问题,况且后续还要自定义 appium-android-driver,appium-uiautomator2-driver 和 appium-uiaumator2-server,工作量至少得 3 天,太费时间。

索性,我来替思寒把 AppCrawler 来升级一下,让它支持最新 appium。

刚开始觉得挺难的,毕竟我对 scala 只略知一二,编译打包方面还要学,但事后发现,这个工程做的确实不错,升级改造过程比预计的要简单很多,这里要给思寒大佬一个赞👍!

改造

先看看出了什么问题?

我开启最新的 appium:

appium

执行 appcrawler 测试:

java -jar appcrawler-2.1.3.jar -a ApiDemos-debug.apk 

执行过程中在 appium 和 appcrawler 两端都报错:

[HTTP] <-- GET /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/window/rect 200 7 ms - 50
[HTTP] 
[HTTP] --> POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync
[HTTP] {"script":"var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;","args":[]}
[debug] [W3C (efdf97d8)] Calling AppiumDriver.execute() with args: ["var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;",[],"efdf97d8-cf46-4ffb-b2d4-7d8feb931cee"]
[debug] [W3C (efdf97d8)] Encountered internal error running command: NotImplementedError: Method is not implemented
[debug] [W3C (efdf97d8)]     at AndroidDriver.extensions.execute (/usr/local/lib/node_modules/appium/node_modules/appium-android-driver/lib/commands/execute.js:12:9)
[debug] [W3C (efdf97d8)]     at curCommandCancellable._bluebird.default.resolve.then (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/lib/basedriver/driver.js:291:18)
[debug] [W3C (efdf97d8)]     at tryCatcher (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/util.js:26:23)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:510:31)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAt (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:584:18)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAtPostResolution (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:248:10)
[debug] [W3C (efdf97d8)]     at Async._drainQueue (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:128:12)
[debug] [W3C (efdf97d8)]     at Async._drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:133:10)
[debug] [W3C (efdf97d8)]     at Immediate.Async.drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:15:14)
[debug] [W3C (efdf97d8)]     at runCallback (timers.js:705:18)
[debug] [W3C (efdf97d8)]     at tryOnImmediate (timers.js:676:5)
[debug] [W3C (efdf97d8)]     at processImmediate (timers.js:658:5)
[HTTP] <-- POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync 405 11 ms - 1600
[HTTP] 
[HTTP] --> POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync
[HTTP] {"script":"var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;","args":[]}
[debug] [W3C (efdf97d8)] Calling AppiumDriver.execute() with args: ["var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;",[],"efdf97d8-cf46-4ffb-b2d4-7d8feb931cee"]
[debug] [W3C (efdf97d8)] Encountered internal error running command: NotImplementedError: Method is not implemented
[debug] [W3C (efdf97d8)]     at AndroidDriver.extensions.execute (/usr/local/lib/node_modules/appium/node_modules/appium-android-driver/lib/commands/execute.js:12:9)
[debug] [W3C (efdf97d8)]     at curCommandCancellable._bluebird.default.resolve.then (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/lib/basedriver/driver.js:291:18)
[debug] [W3C (efdf97d8)]     at tryCatcher (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/util.js:26:23)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:510:31)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAt (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:584:18)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAtPostResolution (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:248:10)
[debug] [W3C (efdf97d8)]     at Async._drainQueue (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:128:12)
[debug] [W3C (efdf97d8)]     at Async._drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:133:10)
[debug] [W3C (efdf97d8)]     at Immediate.Async.drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:15:14)
[debug] [W3C (efdf97d8)]     at runCallback (timers.js:705:18)
[debug] [W3C (efdf97d8)]     at tryOnImmediate (timers.js:676:5)
[debug] [W3C (efdf97d8)]     at processImmediate (timers.js:658:5)
[HTTP] <-- POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync 405 4 ms - 1600
[HTTP] 
[HTTP] --> POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync
[HTTP] {"script":"var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;","args":[]}
[debug] [W3C (efdf97d8)] Calling AppiumDriver.execute() with args: ["var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;",[],"efdf97d8-cf46-4ffb-b2d4-7d8feb931cee"]
[debug] [W3C (efdf97d8)] Encountered internal error running command: NotImplementedError: Method is not implemented
[debug] [W3C (efdf97d8)]     at AndroidDriver.extensions.execute (/usr/local/lib/node_modules/appium/node_modules/appium-android-driver/lib/commands/execute.js:12:9)
[debug] [W3C (efdf97d8)]     at curCommandCancellable._bluebird.default.resolve.then (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/lib/basedriver/driver.js:291:18)
[debug] [W3C (efdf97d8)]     at tryCatcher (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/util.js:26:23)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:510:31)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAt (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:584:18)
[debug] [W3C (efdf97d8)]     at Promise._settlePromiseAtPostResolution (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/promise.js:248:10)
[debug] [W3C (efdf97d8)]     at Async._drainQueue (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:128:12)
[debug] [W3C (efdf97d8)]     at Async._drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:133:10)
[debug] [W3C (efdf97d8)]     at Immediate.Async.drainQueues (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/node_modules/bluebird/js/main/async.js:15:14)
[debug] [W3C (efdf97d8)]     at runCallback (timers.js:705:18)
[debug] [W3C (efdf97d8)]     at tryOnImmediate (timers.js:676:5)
[debug] [W3C (efdf97d8)]     at processImmediate (timers.js:658:5)
[HTTP] <-- POST /wd/hub/session/efdf97d8-cf46-4ffb-b2d4-7d8feb931cee/execute/sync 405 9 ms - 1600
[HTTP]
2019-05-08 11:34:01 WARN [AppiumClient.$anonfun$getPageSource$1.340] get page source error
2019-05-08 11:34:01 WARN [Crawler.refreshPage.562] page source get fail, go back
2019-05-08 11:34:01 INFO [Crawler.setElementAction.660] set action to back
2019-05-08 11:34:01 INFO [Crawler.runStartupScript.236] first refresh
2019-05-08 11:34:01 INFO [Crawler.doElementAction.976] current element = _startupActions-Start-0
2019-05-08 11:34:01 INFO [Crawler.doElementAction.977] current index = 0
2019-05-08 11:34:01 INFO [Crawler.doElementAction.978] current action = 
2019-05-08 11:34:01 INFO [Crawler.doElementAction.979] current url = 
2019-05-08 11:34:01 INFO [Crawler.doElementAction.980] current xpath = startupActions-Start-0
2019-05-08 11:34:01 INFO [Crawler.doElementAction.981] current tag path = _startupActions-Start-0
2019-05-08 11:34:01 INFO [Crawler.doElementAction.982] current file name = _
2019-05-08 11:34:01 INFO [Crawler.doElementAction.983] current uri =    startupActions-Start-0          startupActions
Exception in thread "main" java.util.NoSuchElementException: last of empty ListBuffer
    at scala.collection.mutable.ListBuffer.last(ListBuffer.scala:401)
    at com.testerhome.appcrawler.DataRecord.last(DataRecord.scala:40)
    at com.testerhome.appcrawler.Crawler.doElementAction(Crawler.scala:985)
    at com.testerhome.appcrawler.Crawler.runStartupScript(Crawler.scala:238)
    at com.testerhome.appcrawler.Crawler.start(Crawler.scala:152)
    at com.testerhome.appcrawler.AppCrawler$.startCrawl(AppCrawler.scala:344)
    at com.testerhome.appcrawler.AppCrawler$.parseParams(AppCrawler.scala:312)
    at com.testerhome.appcrawler.AppCrawler$.main(AppCrawler.scala:92)
    at com.testerhome.appcrawler.AppCrawler.main(AppCrawler.scala)

分析原因

我们看到 Appcrawler 中报了个get page source error,我们追查 appcrawler 的代码发现是在这里报错的:

override def getPageSource(): String = {
    currentPageSource=null
    currentPageDom=null
    log.info("start to get page source from appium")
    //获取页面结构, 最多重试3次
    1 to 3 foreach (i => {
      asyncTask(20)(driver.getPageSource) match {
        case Some(v) => {
          log.trace("get page source success")
          //todo: wda返回的不是标准的xml
          val xmlStr=v match {
            case json if json.trim.charAt(0)=='{' => {
              log.info("json format maybe from wda")
              DataObject.fromJson[Map[String, String]](v).getOrElse("value", "")
            }
            case xml if xml.trim.charAt(0)=='<' =>{
              log.info("xml format ")
              xml
            }
          }
          Try(XPathUtil.toDocument(xmlStr)) match {
            case Success(v) => {
              currentPageDom = v
            }
            case Failure(e) => {
              log.warn("convert to xml fail")
              log.warn(xmlStr)
              currentPageDom=null
            }
          }

          currentPageSource = XPathUtil.toPrettyXML(xmlStr)
          return currentPageSource
        }
        case None => {
          log.warn("get page source error")
        }
      }
    })
    currentPageSource
  }

我们看 appium 的源码,发现在 appcrawler 给我们的 appium 传递了一段 js 代码来获取控件树

{"script":"var source = document.documentElement.outerHTML; \nif (!source) { source = new XMLSerializer().serializeToString(document); }\nreturn source;","args":[]}

然而,我们的 appium 代码对 get page source 这个功能接口做了限制,源码在appium-android-driver中的 lib/execute.js 中:

extensions.execute = async function execute (script, args) {
  if (script.match(/^mobile:/)) {
    script = script.replace(/^mobile:/, '').trim();
    return await this.executeMobile(script, _.isArray(args) ? args[0] : args);
  }

  throw new errors.NotImplementedError();
};

我们可以看到,这里抛出异常了,说明可能是接口变动了,那么我这里有个大胆猜想,appcrawler 所使用的 java-client 过老。

解决问题

ok,立马开始行动,替换上最新的 java-client,也就是 7.0,同时我们使用最新的 appcrawler2.4.0

<dependency>
  <groupId>com.github.appium</groupId>
  <artifactId>java-client</artifactId>
  <version>v7.0.0</version>
</dependency>

同时添加对应的仓库:

<repository>
  <id>jitpack.io</id>
  <url>https://jitpack.io</url>
</repository>

打包

mvn assembly:assembly

打包完成,会再 target 目录下生成一个完整依赖的 jar 包:appcrawler-2.4.0-jar-with-dependencies.jar

重新执行,你会发现美妙的事情发生,最新 appium 完美支持!

提出质疑

上面的完美支持,是不是因为我更新了最新版本 2.4.0,而不是使用的最开始的 2.1.3 版本呢?

有可能!!!

撤销修改,直接打包 2.4.0,执行测试看是否正常。

结果就是:最开始的 get page source 问题没了,但出现另外一个问题:

2019-05-08 11:55:46 INFO [AppiumClient.30.initLog] already exist
Exception in thread "main" scala.MatchError: [app, appium, deviceName, dontStopAppOnReset, fullReset, noReset] (of class java.util.Collections$UnmodifiableSet)
        at com.testerhome.appcrawler.driver.AppiumClient.appium(AppiumClient.scala:94)
        at com.testerhome.appcrawler.driver.AppiumClient.<init>(AppiumClient.scala:40)
        at com.testerhome.appcrawler.Crawler.setupAppium(Crawler.scala:277)
        at com.testerhome.appcrawler.Crawler.restart(Crawler.scala:221)
        at com.testerhome.appcrawler.Crawler.crawl(Crawler.scala:201)
        at com.testerhome.appcrawler.Crawler.start(Crawler.scala:170)
        at com.testerhome.appcrawler.AppCrawler$.startCrawl(AppCrawler.scala:323)
        at com.testerhome.appcrawler.AppCrawler$.parseParams(AppCrawler.scala:291)
        at com.testerhome.appcrawler.AppCrawler$.main(AppCrawler.scala:91)
        at com.testerhome.appcrawler.AppCrawler.main(AppCrawler.scala)
chengmingdeMacBook-Pro:AppCrawler cmlanche$ 
[debug] [W3C (dd159942)] Encountered internal error running command: NoSuchDriverError: A session is either terminated or not started
[debug] [W3C (dd159942)]     at asyncHandler (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/lib/protocol/protocol.js:298:15)
[debug] [W3C (dd159942)]     at asyncHandler (/usr/local/lib/node_modules/appium/node_modules/appium-base-driver/lib/protocol/protocol.js:489:15)
[debug] [W3C (dd159942)]     at Layer.handle [as handle_request] (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
[debug] [W3C (dd159942)]     at next (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/route.js:137:13)
[debug] [W3C (dd159942)]     at Route.dispatch (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/route.js:112:3)
[debug] [W3C (dd159942)]     at Layer.handle [as handle_request] (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
[debug] [W3C (dd159942)]     at /usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:281:22
[debug] [W3C (dd159942)]     at param (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:354:14)
[debug] [W3C (dd159942)]     at param (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:365:14)
[debug] [W3C (dd159942)]     at Function.process_params (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:410:3)
[debug] [W3C (dd159942)]     at next (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:275:10)
[debug] [W3C (dd159942)]     at logger (/usr/local/lib/node_modules/appium/node_modules/morgan/index.js:144:5)
[debug] [W3C (dd159942)]     at Layer.handle [as handle_request] (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
[debug] [W3C (dd159942)]     at trim_prefix (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:317:13)
[debug] [W3C (dd159942)]     at /usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:284:7
[debug] [W3C (dd159942)]     at Function.process_params (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:335:12)
[debug] [W3C (dd159942)]     at next (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:275:10)
[debug] [W3C (dd159942)]     at jsonParser (/usr/local/lib/node_modules/appium/node_modules/body-parser/lib/types/json.js:110:7)
[debug] [W3C (dd159942)]     at Layer.handle [as handle_request] (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
[debug] [W3C (dd159942)]     at trim_prefix (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:317:13)
[debug] [W3C (dd159942)]     at /usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:284:7
[debug] [W3C (dd159942)]     at Function.process_params (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:335:12)
[debug] [W3C (dd159942)]     at next (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:275:10)
[debug] [W3C (dd159942)]     at Layer.handle [as handle_request] (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/layer.js:91:12)
[debug] [W3C (dd159942)]     at trim_prefix (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:317:13)
[debug] [W3C (dd159942)]     at /usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:284:7
[debug] [W3C (dd159942)]     at Function.process_params (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:335:12)
[debug] [W3C (dd159942)]     at next (/usr/local/lib/node_modules/appium/node_modules/express/lib/router/index.js:275:10)
[HTTP] <-- GET /wd/hub/session/dd159942-ed6d-411c-8dcc-b43d7fc26284/source 404 5 ms - 3173

还是错的!

那就用我的最新 java-client 7.0 吧,重新执行一次完整的测试,发现没有任何问题!

可以看:https://github.com/cmlanche/AppCrawler/tree/2.3.1 我的修改


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