尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Appium Drivers 深入解析:从 BaseDriver 继承到多层代理架构

Appium Drivers 深入解析:从 BaseDriver 继承到多层代理架构 Appium Drivers 深入解析从 BaseDriver 继承到多层代理架构【免费下载链接】appiumCross-platform automation framework for all kinds of apps, built on top of the W3C WebDriver protocol项目地址: https://gitcode.com/GitHub_Trending/ap/appium在 Appium 中driver驱动是连接 WebDriver 协议与具体平台iOS、Android、桌面、Web 等之间的桥梁它决定了 Appium 能否自动化某个平台。本文以 drivers.md 为核心系统讲解 driver 的本质——一个继承自BaseDriver的 Node.js 类并深入剖析其命令实现机制、平台自动化映射、多层架构与代理Proxy模式。读完你将理解 driver 的内部工作方式掌握在测试遇到问题时快速定位故障层的思路甚至具备编写自己的 Appium driver 的基础知识。一、什么是 Appium Driver正如 Appium in a Nutshell 所述Appium 被拆分为四个部分Appium Core定义核心 API、Drivers实现到具体平台的连接、Clients在特定语言中实现 Appium API与Plugins改变或扩展 Appium 核心功能。Driver 在其中扮演的角色是回答一个根本问题如何支持对多个互不相关的平台进行自动化对大多数使用者而言了解 driver 内部机制的收益主要体现在调试上当测试遇到问题时熟悉 driver 的典型复杂度与架构能帮助你快速定位问题出在哪一层。而对想要编写自己的 driver 或为现有 driver 贡献代码的开发者这部分内容则是必修课。二、最小可用的 Driver继承 BaseDriver从技术角度看一个 Appium driver 本质上就是继承自 Appium 内置的BaseDriver类的 Node.js 类。甚至可以写出一个几乎可用的最小 driverimport BaseDriver from appium/base-driver class MyNewDriver extends BaseDriver { }这个空 driver 本身不做任何事情但你可以把它打包成一个 Node.js 模块在package.json中加上 Appium 相关的字段然后通过appium driver install安装使用。在仓库中BaseDriver的实现位于 packages/base-driver/lib/basedriver/driver.ts它继承自DriverCore并实现了appium/types中的Driver接口。BaseDriver的核心价值在于它封装了整个 WebDriver 协议。因此一个 driver 要做有用的事情只需要实现名字与 WebDriver 协议命令一一对应的 Node.js 方法即可。从源码看这种方法名即协议命令的机制体现在executeCommand方法中driver.ts它以命令字符串为键从 driver 实例上直接查找同名方法const command invoker[cmd]如果找不到就抛出NotYetImplementedError。这就是为什么 driver 作者只需按约定命名方法而无需关心 HTTP 路由细节。如何实现一个协议命令以 Navigate To 为例假设我们想用上面这个空 driver 做点什么第一步是决定要实现哪个 WebDriver 命令。以 W3C 规范的 Navigate To导航到命令为例只需在 driver 类中定义一个setUrl方法async setUrl(url) { // 在这里做任何你想做的事 }你可能会疑惑setUrl和 Navigate To 看起来毫无关联怎么知道要用这个方法名答案在于 Appium 的协议到方法名的映射表。在appium/base-driver包中这个映射定义在 packages/base-driver/lib/protocol/routes/w3c.ts/session/:sessionId/url: { POST: {command: setUrl, payloadParams: {required: [url]}}, },即 HTTP 路由POST /session/:sessionId/url被映射到方法名setUrl并声明必需参数url。这个仓库中的路由定义即是原文档提到的routes.js在当前版本中的形态位于packages/base-driver/lib/protocol/routes/目录下按w3c.ts、mjsonwp.ts、jsonwp.ts、appium.ts等分文件组织。所以编写 driver 时去这个目录查方法名与参数即可参考任何主流 Appium driver 的源码也是好办法。命令如何实现完全由 driver 作者决定取决于要支持的平台。同一个 Navigate To 命令在不同平台的典型实现差异很大浏览器执行 JavaScript 设置window.location.hrefiOS 应用通过 deep link 启动应用Android 应用通过 deep link 启动应用React 应用加载特定路由Unity跳转到指定场景可以看到同一个 WebDriver 命令在不同平台上的实现千差万别但不变的是它们表达能处理某个协议命令的方式——即实现同名方法。仓库中的 FakeDriver 就是一个很好的参考实现它在 packages/fake-driver/lib/driver.ts 中继承BaseDriver通过desiredCapConstraints声明自己的能力约束并以模块化方式commands/目录下的alert.ts、contexts.ts、element.ts、find.ts、general.ts将一组方法挂载到类上例如getUrl、bidiNavigate、click、findElement等。需要强调的一点是driver 本身并不天生属于任何特定平台它只是一段能够处理 WebDriver 协议命令的 JS 代码。它最终变成什么取决于 driver 作者的后续设计。三、自动化映射从 WebDriver 协议到平台 API通常情况下driver 作者的目标是为某个或多个平台提供与浏览器 WebDriver 规范语义高度一致的自动化行为查找元素时返回 UI 元素的引用点击或轻触元素时的行为应与人手操作一致如此等等。因此driver 作者面临的真正挑战不是如何使用 WebDriver 协议BaseDriver已经封装好了这一切而是如何在目标平台上真正执行自动化。每个 driver 都依赖自己的一套底层技术iOS driver 使用苹果的XCUITest技术UiAutomator2 driver 不仅依赖 Google 的UiAutomator2技术还依赖只有通过ADB才可用的功能以及一个辅助 App 中只有通过 Android SDK 才可用的功能。这些底层自动化技术通常拥有专有或特立独行的 API。编写 driver 的工作就变成把 WebDriver 协议映射到这个底层 API有时是一组不同的底层 API再把它们整合成统一、可用的 WebDriver 接口——这正是 driver 开发中最有价值也最具挑战性的艺术。值得注意的是如今BaseDriver还通过 packages/base-driver/lib/basedriver/extension-core.ts 提供了对 WebDriver BiDi 命令的支持driver 可以声明自己的 BiDi 模块与命令bidiCommands并通过executeBidiCommand按moduleName.methodName的形式执行最终同样落到以协议约定命名的方法处理器上。这让 driver 在传统 WebDriver 命令之外还能扩展支持新一代的 BiDi 自动化能力FakeDriver 中即有NEW_BIDI_COMMANDS的示例。四、多层架构以 XCUITest driver 为例在实践中driver 架构往往相当复杂。以 iOS 为例XCUITest 框架要求调用它的代码用 Objective-C 或 Swift 编写而且 XCUITest 代码只能在 Xcode直接或间接地包括 Xcode 命令行工具触发的特殊模式下运行。换句话说不存在一条从 Node.js 函数实现如上面的setUrl直达 XCUITest API 调用的简单路径。XCUITest driver 作者采取的方案是把 driver 拆成两部分Node.js 部分被集成进 Appium最初处理 WebDriver 命令Objective-C 部分真正运行在 iOS 设备上、发起 XCUITest API 调用。这种拆分让对接 XCUITest 成为可能但也引入了新问题两部分之间如何协调通信driver 作者最终选择的通信方式出人意料又顺理成章——还是 WebDriver 协议Objective-C 那一半本身就是 WebDriver 实现它叫做WebDriverAgent。理论上你甚至可以把 WebDriver 客户端直接指向 WebDriverAgent 而完全绕过 Appium。但这通常并不方便原因有二Appium XCUITest driver 会为你构建和管理 WebDriverAgent这本身涉及 Xcode比较麻烦XCUITest driver 能做的远不止 WebDriverAgent 的能力范围例如管理模拟器或真机、安装 App 等。这个例子的启示是由于问题的本质driver 架构可能变得相当复杂、多层化。这也意味着当某个测试出问题时有时很难判断故障出在整个技术链的哪一环。以 XCUITest 世界为例同一时刻可能牵涉的技术栈包括你的测试代码某种编程语言——归你所有Appium 客户端库——归 AppiumSelenium 客户端库——归 Selenium网络本地或互联网Appium 服务器——归 AppiumAppium XCUITest driver——归 AppiumWebDriverAgent——归 AppiumXcode——归 AppleXCUITest——归 AppleiOS 本身——归 ApplemacOS运行 Xcode 与 iOS 模拟器——归 Apple这是一条相当深的调用栈排查问题时沿着这条链路逐层检查测试代码 → 客户端库 → 网络 → Appium 服务器 → driver → 下游 WebDriver 服务 → 系统 API往往能快速定位问题所在。五、代理模式把命令直接转发给下游 WebDriver除了多层架构driver 还有一个重要的架构特性可以用 XCUITest driver 再次说明。既然 driver 的两半都讲 WebDriver 协议——Node.js 一半直接嵌入 Appium 的 WebDriver 服务器Objective-C 一半WebDriverAgent则是独立的 WebDriver 实现——这就为 Appium 在某些情况下抄近路创造了可能。设想 XCUITest driver 需要实现 Click Element 命令。其内部实现无非是取适当参数向 WebDriverAgent 服务器构造一个 HTTP 请求。这本质上就是把客户端最初发给 Appium 服务器的调用重新构造了一遍不完全相同——Appium 服务器与 WebDriverAgent 服务器会生成不同的 session ID但这些差异会被透明处理。既然如此根本没有必要为 Click Element 编写实现函数XCUITest driver 只需让 Appium 知道这个命令应该被直接代理到某个下游 WebDriver 服务器。这里的代理proxying含义是XCUITest driver 完全不会参与该命令的处理命令在协议层面被重新打包并转发给 WebDriverAgentWebDriverAgent 的响应也直接回传给客户端中间没有任何 XCUITest driver 的代码看到或修改它。这个架构模式给处处使用 WebDriver 协议、而非自造专有协议的 driver 作者带来了很好的红利Appium 可以非常容易地为任何其他现存的 WebDriver 实现创建包装型wrapperdriver。例如 Appium Safari driver 几乎没有实现任何标准命令因为所有这些命令都被直接代理到下游的 SafariDriver 进程。理解这个代理行为很重要当你深入某个开源 driver 代码想弄清某个命令是在哪里实现的时候可能会惊讶地发现 Node.js driver 代码里根本没有实现这种情况下你需要弄清楚命令被代理到了哪里然后去那个位置寻找真正的实现。在仓库源码中代理能力由 packages/base-driver/lib/jsonwp-proxy/proxy.ts 中的JWProxy类提供它封装了向下游服务器downstream server转发请求所需的一切——协议转换ProtocolConverter、请求超时默认DEFAULT_REQUEST_TIMEOUT 240000毫秒、会话 ID 处理、HTTP/HTTPS 连接池keepAlive默认开启等。同时DriverCore 定义了proxyActive()接口driver 可以借此告知 Appium 当前会话是否处于代理模式。FakeDriver 中也有完整的演示它在 driver.ts 中实现了proxyActive、canProxy、proxyReqRes与proxyCommand四个方法用于模拟命令被转发给下游的行为相关的验证测试可以在 packages/fake-driver/test/e2e/driver.e2e.spec.ts 中找到。六、小结从理解到实战回顾全文driver 的核心知识可以归纳为四点本质driver 就是继承自BaseDriver的 Node.js 类BaseDriver封装了整个 WebDriver 协议命令实现实现与协议命令同名的方法如setUrl路由到方法名的映射见 packages/base-driver/lib/protocol/routes/ 目录方法未实现时会被BaseDriver以NotYetImplementedError拒绝平台自动化真正的工作量在于把 WebDriver 语义映射到平台专有 APIXCUITest、UiAutomator2 ADB 等架构模式driver 常被拆成多层Node.js 平台侧层间甚至用 WebDriver 协议通信并且大量命令可走代理模式直接转发给下游 WebDriver 服务器。对普通用户而言理解这些细节的意义在于调试当测试失败时你可以顺着测试代码 → 客户端库 → 网络 → Appium → driver → 平台自动化服务这条链逐层排查并留意那些在 driver 源码里根本找不到实现的命令——它们很可能被代理到了下游服务。而对想参与 driver 生态的开发者可以参考 packages/fake-driver 这一完整的参考实现从继承BaseDriver、声明desiredCapConstraints、挂载命令方法开始写自己的第一个 driver并通过appium driver install安装使用。【免费下载链接】appiumCross-platform automation framework for all kinds of apps, built on top of the W3C WebDriver protocol项目地址: https://gitcode.com/GitHub_Trending/ap/appium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表