首先,大家都知道 appium 在 android 平台上的底层实现使用的是 UIAutomator 。而 UIAutomator 是具有应用无关的特性的,即不需要打开应用即可控制系统界面。然而 appium 默认 把启动应用与执行测试绑定在了一起,因此无法使用像 UIAutomator 那样应用无关的方式执行用例。
接下来,我会简单介绍一下 appium 在 android 平台初始化 session 的主要步骤,并告诉大家如何在修改数行代码后实现不启动应用直接执行用例。
考虑到这部分不是最重点的部分,因此用文字说明,方便大家理解。
此处的初始化 session 指脚本中 webdriver.Remote('http://localhost:4723/wd/hub,desired_caps') 这个初始化 driver 的语句。
Trying to run a session for device 'android' but that device hasn't been configured. Run config 错误。从上面的步骤可以看到,和 uiautomator 相关的操作只有:
而安装/启动被测应用则是不同的操作。两者耦合度很低,意味着我们可以通过去掉安装/启动的步骤来实现不启动应用完成 session 初始化。
首先,上面提到的配置 android 平台的 session 的主要过程都在这个函数中:
lib/devices/android/android.js
... Android.prototype.start = function (cb, onDie) { this.launchCb = cb; this.uiautomatorExitCb = onDie; logger.info("Starting android appium"); async.series([ this.initJavaVersion.bind(this), this.initAdb.bind(this), this.packageAndLaunchActivityFromManifest.bind(this), this.initUiautomator.bind(this), this.prepareDevice.bind(this), this.checkApiLevel.bind(this), this.pushStrings.bind(this), this.processFromManifest.bind(this), this.uninstallApp.bind(this), this.installAppForTest.bind(this), this.forwardPort.bind(this), this.pushAppium.bind(this), this.initUnicode.bind(this), this.pushSettingsApp.bind(this), this.pushUnlock.bind(this), function (cb) {this.uiautomator.start(cb);}.bind(this), this.wakeUp.bind(this), this.unlock.bind(this), this.getDataDir.bind(this), this.setupCompressedLayoutHierarchy.bind(this), this.startAppUnderTest.bind(this), this.initAutoWebview.bind(this), this.setActualCapabilities.bind(this) ], function (err) { if (err) { this.shutdown(function () { this.launchCb(err); }.bind(this)); } else { this.didLaunch = true; this.launchCb(null, this.proxySessionId); } }.bind(this)); };
非常一目了然。注释掉和应用安装/启动相关的函数后,它会变成这个样子:
Android.prototype.start = function (cb, onDie) {
  this.launchCb = cb;
  this.uiautomatorExitCb = onDie;
  logger.info("Starting android appium");
  async.series([
    this.initJavaVersion.bind(this),
    this.initAdb.bind(this),
    //this.packageAndLaunchActivityFromManifest.bind(this),
    this.initUiautomator.bind(this),
    this.prepareDevice.bind(this),
    this.checkApiLevel.bind(this),
    //this.pushStrings.bind(this),
    //this.processFromManifest.bind(this),
    //this.uninstallApp.bind(this),
    //this.installAppForTest.bind(this),
    this.forwardPort.bind(this),
    this.pushAppium.bind(this),
    this.initUnicode.bind(this),
    this.pushSettingsApp.bind(this),
    this.pushUnlock.bind(this),
    function (cb) {this.uiautomator.start(cb);}.bind(this),
    this.wakeUp.bind(this),
    this.unlock.bind(this),
    this.getDataDir.bind(this),
    this.setupCompressedLayoutHierarchy.bind(this),
    //this.startAppUnderTest.bind(this),
    //this.initAutoWebview.bind(this),
    this.setActualCapabilities.bind(this)
  ], function (err) {
    if (err) {
      this.shutdown(function () {
        this.launchCb(err);
      }.bind(this));
    } else {
      this.didLaunch = true;
      this.launchCb(null, this.proxySessionId);
    }
  }.bind(this));
};
 
这个时候所有启动应用的相关操作都不会进行。 session 建立后我们仍然能通过 appium 测试脚本控制手机界面,绝大部分操作仍然能正常进行。
# -*- coding:utf-8 -*-
import os
from appium import webdriver
# Returns abs path relative to this file and not cwd
PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)
if __name__ == "__main__":
    desired_caps = {}
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '4.4'
    desired_caps['deviceName'] = 'e4d42545'
    desired_caps['unicodeKeyboard'] = 'true'
    desired_caps['resetKeyboard'] = 'true'
    desired_caps['app'] = PATH(
        '../app/Dianping_dianping-web_7.1.1.apk'
    )
    driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
    print "Start driver success!"
    print "Trying to click browser icon"
    driver.find_element_by_xpath('//android.widget.TextView[@text="Browser"]').click()
    print "Finish clicking browser icon. The browser should be opened."
    driver.quit()
 
