ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Appium universal-xml-plugin:把 iOS 与 Android 页面源码统一为一套通用 XML 语法

Appium universal-xml-plugin:把 iOS 与 Android 页面源码统一为一套通用 XML 语法 Appium universal-xml-plugin把 iOS 与 Android 页面源码统一为一套通用 XML 语法【免费下载链接】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 仓库中的appium/universal-xml-plugin插件文档README为主体结合 lib/plugin.ts、lib/source.ts、lib/xpath.ts 等源码实现完整讲清这个插件的工作机制如何安装启用、如何把两个平台的 Page Source 统一为通用节点/属性命名、XPath 查询如何被自动翻译回平台原生 XML以及映射表、过滤属性的完整清单。读完你将掌握跨平台 UI 测试中“一份源码、一套 XPath”的完整用法与实现原理。1. 解决什么问题Appium 中 iOS 与 Android 驱动返回的页面源码Page Source命名体系完全不同iOS 使用XCUIElementTypeButton这类 XCUITest 元素类型和name/label/visible属性Android 使用android.widget.Button这类控件全名和content-desc/text/displayed属性。这意味着同一个“登录按钮”在两个平台需要写两套 XPath跨平台测试无法复用。appium/universal-xml-plugin的目标README 中的 Motivation正是让 iOS 与 Android 的 XML 源码具备互操作性从而简化跨平台测试的编写。插件把 Get Page Source 返回的 XML 中的节点名、属性名统一改写为一套对两个平台都适用的通用术语如Button、text、visible未收录在映射表中的名称原样保留。从 package.json 可以看到该插件的元信息npm 包名appium/universal-xml-pluginappium.pluginName为universal-xml主类为UniversalXMLPluginpeerDependencies要求appium ^3.0.0-beta.0engines要求 Node^20.19.0 || ^22.12.0 || 24.0.0。2. 安装与启用README 给出的两步操作即为完整用法# 安装插件 appium plugin install universal-xml# 启动 Appium 服务器时显式激活插件 appium --use-pluginsuniversal-xml与所有 Appium 插件一样插件不会自动生效必须在启动服务器时通过--use-plugins显式激活。插件激活后会拦截两类命令并做转换Get Page Source命令返回的应用源码Find Element / Find Elements系列命令中携带的节点名与属性名即 XPath 选择器。3. 拦截机制插件到底 hook 了哪些命令UniversalXMLPlugin 继承自 Appium 的BasePlugin只实现了三个方法getPageSource、findElement、findElements。3.1 拦截 Get Page SourcegetPageSourceplugin.ts的流程是先调用next()拿到驱动返回的原始 XML或兜底直接调driver.getPageSource()从会话能力caps.platformName中取出平台名getPlatformName若平台为 Android额外从驱动选项driver.opts.appPackage取包名放入转换元数据——这个包名稍后会用于剥离resource-id的前缀调用 transformSourceXml 完成真正的 XML 转换返回统一命名后的字符串。此外还有一个值得注意的“质量反馈”机制转换过程中所有未被映射表收录的节点名与属性名会被收集起来插件会在日志中打 warn提示“这些未知名称应该被报告以改进插件质量”plugin.ts。也就是说源码里保留了完整的 unknown 统计通道只是默认行为是“原样保留 告警”。3.2 拦截 Find Element 系列命令findElement/findElements共用内部方法_findplugin.ts其前置条件非常明确源码中只有同时满足以下三点才会走翻译逻辑否则直接放行给驱动next()定位策略strategy必须是xpath不区分大小写驱动实现了getCurrentContext当前上下文必须是NATIVE_APP。满足条件后的执行链是先以addIndexPath: true模式重新生成一份带索引路径的转换后 XML见第 5 节用 transformQuery 把用户写的“通用 XPath”翻译成针对原始平台 XML的等价路径表达式若翻译结果为null意味着没有匹配节点或翻译失败插件记录 warn然后findElement抛NoSuchElementError、findElements返回空数组[]翻译成功则记录Selector was translated to: ...的 info 日志并直接调用driver.findElement/findElements(strategy, newSelector)——注意这里是直接调驱动而不是next()因为翻译后的选择器必须作用于未经插件改写的原生 XML。4. 源码转换核心解析、递归改写与命名映射transformSourceXml 基于fast-xml-parser的单例XMLParser/XMLBuilder实现属性前缀常量ATTR_PREFIX为_source.ts输出前会补上?xml version1.0 encodingUTF-8?声明。解析器配置中isArray恒返回 true对非属性节点保证重复节点名合并时能正确形成数组source.ts。4.1 节点名映射多对一映射数据结构定义在 types.ts键是通用节点名值按平台给出字符串或字符串数组即支持多个平台名映射到同一个通用名many-to-one。查找函数 getUniversalName 遍历整张表做包含判断未命中返回null。改名时 transformChildNodes 先递归处理子树再替换节点名当两个不同的原名映射到同一通用名时它会把原有值包成数组并合并source.ts避免覆盖丢失。README 中给出的三行示例Button/Alert/SwitchInput在 node-map.ts 中有完整映射表这里给出全文iOS/Android 均为数组时以顿号分隔—表示该平台无对应来源名通用节点名iOS 来源Android 来源AlertXCUIElementTypeAlertandroid.widget.ToastAppXCUIElementTypeApplication—ButtonXCUIElementTypeButton、DecrementArrow、IncrementArrow、DisclosureTriangle、Handle、Key、Link、MenuButton、PageIndicator、PopUpButton、ToolbarButton、RadioButton、Tabandroid.widget.Button、ImageButton、RadioButton、QuickContactBadgeCellXCUIElementTypeCell—CheckBoxXCUIElementTypeCheckBoxandroid.widget.CheckBoxColumnXCUIElementTypeTableColumn—DateInputXCUIElementTypeDatePickerandroid.widget.DatePickerElementXCUIElementTypeOther、Any、Matte、MenuBarItem、MenuItem、Ruler、RulerMarker、Splitter、StatusItem、Timelineandroid.widget.Space、TwoLineListItemGridXCUIElementTypeGridandroid.widget.GridLayout、GridViewIconXCUIElementTypeIcon、DockItem—ImageXCUIElementTypeImageandroid.widget.ImageViewIndicatorXCUIElementTypeLevelIndicator、ProgressIndicator、RatingIndicator、RelevanceIndicator、ValueIndicatorandroid.widget.RatingBar、ProgressBarInputXCUIElementTypeColorWell—ListXCUIElementTypeCollectionViewandroid.widget.ListView、ExpandableListView、GalleryMapXCUIElementTypeMap—MenuXCUIElementTypeMenu、MenuBarandroid.widget.ActionMenuView、PopupMenuModalXCUIElementTypeDrawer、Dialog、Popoverandroid.widget.ListPopupWindow、PopupWindow、SlidingDrawer、MagnifierNavXCUIElementTypeNavigationBar—PickerInputXCUIElementTypePickerWheelandroid.widget.NumberPicker、TimePicker、CalendarViewRadioInputXCUIElementTypeRadioGroupandroid.widget.RadioGroupRowXCUIElementTypeTableRow、OutlineRow、SegmentedControl、TouchBarandroid.widget.TableRowScrollableXCUIElementTypeScrollViewandroid.widget.ScrollView、HorizontalScrollViewSearchInputXCUIElementTypeSearchFieldandroid.widget.SearchViewSliderInputXCUIElementTypeSlider、Stepper、ScrollBarandroid.widget.SeekBarSpinnerXCUIElementTypeActivityIndicatorandroid.widget.SpinnerSwitchInputXCUIElementTypeSwitchandroid.widget.SwitchTableXCUIElementTypeTableandroid.widget.TableLayoutTextXCUIElementTypeStaticText、TextView、HelpTagandroid.widget.TextView、Chronometer、TextClockTextInputXCUIElementTypeTextField、SecureTextField、ComboBoxandroid.widget.EditText、AutoCompleteTextView、MultiAutoCompleteTextViewToggleInputXCUIElementTypeToggleandroid.widget.CheckedTextView、ToggleButtonToolbarXCUIElementTypeToolbarandroid.widget.ToolbarUIAppiumAUThierarchyVideo—android.widget.VideoViewViewXCUIElementTypeBrowser、Group、Keyboard、LayoutArea、LayoutItem、Outline、Picker、Sheet、SplitGroup、StatusBar、TabBar、TabGroupandroid.widget.FrameLayout、LinearLayout、RelativeLayout、android.view.View、ViewGroup、MediaController、StackViewWebViewXCUIElementTypeWebView—WindowXCUIElementTypeWindow—上表中 iOS 来源为完整XCUIElementTypeXxx名的缩写展示以 node-map.ts 中的全量定义为准。未收录的名称不做任何改写仅计入 unknown 统计并触发第 3.1 节的告警日志。4.2 属性名映射与移除清单属性映射表 ATTR_MAP 与 README 中三行示例对应的完整内容是通用属性iOS 来源Android 来源axIdnamecontent-desctextlabeltextvisiblevisibledisplayedx/y/width/height同名由 bounds 换算见 4.3同名由 bounds 换算见 4.3id—resource-idenabledenabled见下方说明valuevalue—README 同时强调“插件还会从转换后的 XML 中删除若干属性”即 REMOVE_ATTRSindex, type, package, class, checkable, checked, clickable, enabled, focusable, focused, long-clickable, password, scrollable, selected, bounds, rotation从源码看transformAttrs 中对每个属性先判断是否命中 REMOVE_ATTRS、命中即删除再做映射查找。因此上表中enabled一行存在一个平台差异iOS 的enabled会映射为通用enabled保留Android 的enabled因先命中移除清单而被直接丢弃。同理 Android 的bounds虽在移除清单中但它的坐标信息会先被平台转换器换算为x/y/width/height见下节信息并未丢失。4.3 平台预处理转换器Android 的 bounds 与 resource-id除了改名transformNode在每个节点上还会调用平台转换器source.tsiOS 转换器是空操作transformers.ts因为 XCUITest 源码本身已带有x/y/width/height与短name属性Android 转换器transformers.ts做两件事把resource-id形如com.example:id/title的值剥离${appPackage}:id/前缀包名即第 3.1 节从driver.opts.appPackage取的元数据最终呈现为短id属性把bounds[x,y][x2,y2]拆分解析换算出x、y、width、height四个通用属性。以测试夹具 test/fixtures/android.xml 中的一行为例其中android.widget.EditText节点带有content-descusername、textalice、bounds[150,504][930,616]、displayedtrue。按上述规则它会被改写为TextInput节点并携带axIdusername、textalice、visibletrue、x150、y504、width780、height112其余index/class/clickable等命中移除清单的属性则被丢弃。转换前后对照可参考夹具中的 android-transformed.xml 与 ios-transformed.xml。4.4 indexPath为 XPath 反查埋下的索引transformNode 在addIndexPath开启时要求每个节点必须带index属性缺失则直接抛错并把父路径拼接成parent/index形式写入indexPath属性。源码注释里特别说明了一个不对称处理source.tsiOS 的AppiumAUT根节点被 XCUITest 驱动排除在查询层级之外因此故意不给它写 indexPathAndroid 的hierarchy根则参与查询层级保留其索引。5. XPath 选择器的翻译原理transformQuery 是整个插件最巧妙的部分思路是“在转换后的 XML 上先跑一遍用户选择器再把命中的节点按 indexPath 反写成原生 XML 中的位置表达式”用xmldom/xmldomxpath库在转换后、带 indexPath 的 XML上执行用户的通用 XPathrunQuery过滤掉没有indexPath的节点即第 4.4 节中的 iOSAppiumAUT根把每个命中节点的indexPath形如/0/0/1/1/0/1/0/2逐段 1XPath 索引从 1 开始映射为*[n]位置轴表达式并重新拼接例如/0/0/1变为/*[1]/*[1]/*[2]——由于插件改名是保序的位置轴路径在未改名的原生 XML 上同样指向原节点单个查询取第一条结果多元素查询findElements用|合并所有命中路径xpath.ts。这一机制的代价与边界也随之明确选择器必须在原生 XML 上唯一可定位翻译走的是“位置轴”而非属性匹配因此页面在获取源码与实际查询之间发生重排时可能定位漂移另外只有xpath策略 NATIVE_APP上下文会被翻译accessibility id、class name等其他策略直接透传给驱动处理。6. 验证与测试入口仓库为该插件提供了可直接运行的验证材料夹具test/fixtures/ 下成对存放原始与转换结果如 android.xml ↔ android-transformed.xml、ios.xml ↔ ios-transformed.xml另有边界场景夹具 ios-edge.xml 与 web-view.xml单测test/unit/plugin.spec.ts 验证插件命令拦截test/unit/source.spec.ts 验证 XML 转换test/unit/xpath.spec.ts 验证选择器翻译CLI 入口index.ts 暴露了一个命令行转换工具用法为node 构建产物/index.js xmlDataPath platform [optsJson]把转换结果打印到 stdout、unknown 统计打印到 stderr也支持--smoke-test冒烟检查对应test:smoke脚本。7. 小结与使用边界该插件是 Appium 官方 monorepo 内的独立包插件名universal-xml通过appium plugin install universal-xml安装、--use-pluginsuniversal-xml激活它统一的是节点名 属性名 XPath 查询三层Page Source 输出通用 XMLnode-map.ts 与 attr-map.ts 定义全部映射XPath 则通过 indexPath 反查被翻译成原生位置表达式xpath.ts未收录的名称/属性原样保留并打 warn 日志映射表以源码文件为准持续扩展适用边界仅拦截xpath策略且上下文为NATIVE_APP的元素查找findElements在无匹配时返回空数组而非报错bounds换算、resource-id剥离等预处理目前只针对 Android 源iOS 转换器为空操作这些行为均可从 transformers.ts 直接印证。【免费下载链接】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),仅供参考
返回列表