跳至内容

Appium 驱动简介

正如 主要概述 所述,“驱动程序”本质上是 Appium 对“如何支持多个无关平台的自动化?”问题的答案。在本文件中,我们将更详细地了解驱动程序的工作原理。除非您打算编写自己的驱动程序或为现有驱动程序做出贡献(我们希望您这样做!),否则驱动程序工作原理的具体细节可能对您来说并不重要。

了解驱动程序工作原理的另一个主要好处是,了解典型的复杂性或典型的驱动程序架构将有助于您在测试中不可避免地遇到问题时进行调试。

接口实现

在最基本的层面上,驱动程序只是扩展了 Appium 中包含的一个特殊类的 Node.js 类,称为 BaseDriver。您可以使用以下非常简单的代码行创建非常接近“工作”驱动程序的东西

import BaseDriver from '@appium/base-driver'

class MyNewDriver extends BaseDriver {
}

这个空驱动程序什么也不做,但您可以将其包装在 Node.js 模块中,在模块的清单(package.json)中添加一些与 Appium 相关的字段,然后使用 appium driver install 安装它。

因此,从技术角度来看,Appium 驱动程序只是一些从其他 Appium 代码继承的代码。就是这样!现在,从 BaseDriver 继承实际上给了我们很多,因为 BaseDriver 本质上是整个 WebDriver 协议的封装。因此,驱动程序要执行一些有用的操作,只需要实现与 WebDriver 协议等效的名称相对应的 Node.js 方法。

因此,假设我想用这个空驱动程序做点什么;首先我必须决定要实现哪个 WebDriver 命令。以我们的示例为例,让我们使用 导航到 WebDriver 命令。暂时先不考虑我希望驱动程序在执行此命令时执行的操作。要告诉 Appium 驱动程序可以处理该命令,我们只需要在驱动程序类中定义如下方法:1

async setUrl(url) {
    // do whatever we want here
}

就是这样!我们如何实际实现该命令完全取决于我们,并且取决于我们想要支持的平台。以下是针对不同平台的此命令的不同示例实现

  • 浏览器:执行一些 JavaScript 来设置 window.location.href
  • iOS 应用程序:使用深层链接启动应用程序
  • Android 应用程序:使用深层链接启动应用程序
  • React 应用程序:加载特定路由
  • Unity:转到命名场景

因此,您可以看到,驱动程序在跨平台实现相同 WebDriver 命令时,可能会存在很多差异。2 但是,相同的是,它们如何表达它们可以处理协议命令。

我们之所以要详细介绍这些内容(顺便说一下,您不需要记住这些内容),是因为强调一点很重要,即 Appium 驱动程序本身并不是任何特定的事物,而只是一些可以处理 WebDriver 协议命令的 JS 代码。从那里开始,就取决于您,驱动程序作者!

自动化映射

但是,通常驱动程序作者想要做的是为给定平台提供自动化行为,这些行为在语义上与浏览器 WebDriver 规范实现非常相似。当您想要查找元素时,您应该获得对 UI 元素的引用。当您想要单击或点击该元素时,产生的行为应该与一个人单击或点击该元素相同。等等。

因此,驱动程序作者面临的真正挑战不是如何使用 WebDriver 协议(因为 BaseDriver 为您封装了所有这些内容),而是如何使实际自动化在目标平台上发生。每个驱动程序都依赖于自己的一组底层技术。如 概述 中所述,iOS 驱动程序使用 Apple 的一项名为 XCUITest 的技术。这些底层自动化技术通常有自己的专有或特殊 API。编写驱动程序就变成了将 WebDriver 协议映射到此底层 API(有时是一组不同的底层 API——例如,UiAutomator2 驱动程序不仅依赖于 Google 的 UiAutomator2 技术,而且还依赖于仅通过 ADB 可用的函数,以及仅通过助手应用程序中的 Android SDK 可用的函数)。将所有这些整合到一个单一的、可用的、WebDriver 接口中,是驱动程序开发中令人难以置信地有用(但极具挑战性)的艺术!

多层架构

在实践中,这通常会导致非常复杂的架构。让我们再次以 iOS 为例。XCUITest 框架(Appium 驱动程序使用的框架)期望调用它的代码是用 Objective-C 或 Swift 编写的。此外,XCUITest 代码只能在 Xcode(以及直接或间接地,Xcode 命令行工具)触发的特殊模式下运行。换句话说,没有直接的方法可以从 Node.js 函数实现(如上面的 setUrl())到 XCUITest API 调用。

XCUITest 驱动程序作者所做的是将驱动程序分成两部分:一部分用 Node.js 编写(这部分被合并到 Appium 中,并最初处理 WebDriver 命令),另一部分用 Objective-C 编写(这部分实际上在 iOS 设备上运行,并进行 XCUITest API 调用)。这使得与 XCUITest 的接口成为可能,但也带来了协调两部分之间通信的新问题。

驱动程序作者可以选择许多不同的策略来模拟 Node.js 端和 Objective-C 端之间的通信,但最终决定使用... WebDriver 协议!没错,XCUITest 驱动程序的 Objective-C 端本身就是一个 WebDriver 实现,称为 WebDriverAgent3

  • Appium XCUITest 驱动程序为您构建和管理 WebDriverAgent,这可能很麻烦,并且需要使用 Xcode。
  • XCUITest 驱动程序的功能远不止 WebDriverAgent 可以实现的功能,例如使用模拟器或设备、安装应用程序等等。

故事的寓意是,驱动程序架构可能会变得非常复杂和多层,这是因为我们试图解决的问题的性质。这也意味着,如果您在特定测试中遇到问题,有时很难确定此技术链中的哪个环节出了问题。再次以 XCUITest 世界为例,我们有以下一组技术同时发挥作用

  • 您的测试代码(以其编程语言编写) - 由您拥有
  • Appium 客户端库 - 由 Appium 拥有
  • Selenium 客户端库 - 由 Selenium 拥有
  • 网络(本地或互联网)
  • Appium 服务器 - 由 Appium 拥有
  • Appium XCUITest 驱动程序 - 由 Appium 拥有
  • WebDriverAgent - 由 Appium 拥有
  • Xcode - 由 Apple 拥有
  • XCUITest - 由 Apple 拥有
  • iOS 本身 - 由 Apple 拥有
  • macOS(Xcode 和 iOS 模拟器运行的地方) - 由 Apple 拥有

这是一个非常深的堆栈!

代理模式

还有一个重要的驱动程序架构方面需要了解。它可以通过 XCUITest 驱动程序再次举例说明。回想一下,我们刚刚讨论了 XCUITest 驱动程序的两个“部分”如何都使用 WebDriver 协议——Node.js 部分直接进入 Appium 的 WebDriver 服务器,而 Objective-c 部分(WebDriverAgent)是它自己的 WebDriver 实现。

这为 Appium 在某些情况下采取捷径打开了可能性。假设 XCUITest 驱动程序需要实现 Click Element 命令。此实现的内部代码看起来像是获取适当的参数并构建到 WebDriverAgent 服务器的 HTTP 请求。在这种情况下,我们基本上只是重建了客户端对 Appium 服务器的原始调用!4 所以实际上甚至不需要编写一个实现 Click Element 命令的函数。相反,XCUITest 驱动程序可以简单地让 Appium 知道此命令应该直接代理到其他 WebDriver 服务器。

如果您不熟悉“代理”的概念,在这种情况下,它只是意味着 XCUITest 驱动程序将完全不参与处理命令。相反,它将仅仅被重新打包并转发到协议级别的 WebDriverAgent,WebDriverAgent 的响应也将直接传递回客户端,而不会有任何 XCUITest 驱动程序代码看到或修改它。

这种架构模式为选择在所有地方处理 WebDriver 协议而不是构建定制协议的驱动程序作者提供了很好的奖励。这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。例如,如果您查看 Appium Safari 驱动程序 代码,您会发现它基本上没有实现任何标准命令,因为所有这些命令都直接代理到底层的 SafariDriver 进程。

重要的是要了解这种代理业务有时是在幕后发生的,因为如果您正在深入研究一些开源驱动程序代码试图找出命令是在哪里实现的,您可能会惊讶地发现 Node.js 驱动程序代码本身根本没有实现!在这种情况下,您需要弄清楚命令被代理到哪里,以便您可以在那里查找相应的实现。

好了,关于驱动程序的这个非常详细的介绍就到这里了!


  1. 您可能会注意到 setUrl 看起来不像 Navigate To,那么我们怎么知道使用它而不是其他一些随机字符串呢?好吧,Appium 的 WebDriver 协议到方法名称的映射是在 @appium/base-driver 包中的一个特殊文件中定义的,名为 routes.js。因此,如果您正在编写驱动程序,这就是您应该去的地方,以了解要使用哪些方法名称以及要期望哪些参数。或者您可以查看任何主要 Appium 驱动程序的源代码! 

  2. 当然,我们希望保持语义尽可能相似,但在 iOS 世界中,例如,通过深度链接(带有特殊应用程序特定方案的 URL)启动应用程序与我们尽可能接近导航到网页 URL。 

  3. 因此,理论上,您可以将您的 WebDriver 客户端直接指向 WebDriverAgent 并完全绕过 Appium。但是,由于以下几个原因,这通常不方便: 

  4. 它并不完全相同,因为 Appium 服务器和 WebDriverAgent 服务器将生成不同的会话 ID,但这些差异将被透明地处理。