SuperAPI AI 工具集

composing-semantics-matchers

测试与质量

查看项目

更新于 10-16 02:18

技能介绍

用途

本技能解决 Compose UI 测试中“如何精确查找节点”的问题。核心思路是:把多个查询条件组合成一个 SemanticsMatcher,而不是先找到节点再链式调用多个 assertIs* 断言。组合后的匹配器自带可读的描述字符串,断言失败时会原样打印,逐条列出未满足的子句,从而让失败原因一目了然。

主要能力

  1. 预置过滤器库:Compose 在 Filters.kt 中提供丰富的现成匹配器,按信号分组,包括启用/禁用(isEnabled、isNotEnabled)、开关(isToggleable、isOn、isOff)、选中(isSelectable、isSelected、isNotSelected)、焦点(isFocusable、isFocused 等)、点击动作(hasClickAction、hasNoClickAction)、滚动动作(hasScrollAction、hasScrollToIndexAction、hasScrollToKeyAction、hasScrollToNodeAction)、文本动作(hasSetTextAction、hasInsertTextAtCursorAction、hasPerformImeAction、hasImeAction)、内容描述(hasContentDescription、hasContentDescriptionExactly)、文本(hasText、hasTextExactly)、无障碍与状态元数据(hasStateDescription、hasProgressBarRangeInfo、isHeading、isDialog、isPopup、isHiddenFromAccessibility、isEditable、isRoot)、测试标签(hasTestTag)以及层级匹配器(hasParent、hasAnyChild、hasAnySibling、hasAnyAncestor、hasAnyDescendant)。其中 hasText 会同时搜索 Text、EditableText 和 InputText;hasContentDescription 匹配合并后的描述列表。

  2. 组合器:通过 infix and、infix or 和 operator not 生成新的匹配器,其描述分别形如 (left) && (right)、(left) || (right)、NOT (inner)。

  3. 自定义语义键:生产代码通过 Modifier.semantics { customKey = value } 写入自定义 SemanticsPropertyKey 后,测试可用 SemanticsMatcher.expectValue(customKey, expectedValue) 匹配具体值,用 keyIsDefined 判断键是否被设置,用 keyNotDefined 判断键未设置。内置匹配器如 isOn() 本身也是 expectValue 的薄封装。

  4. 层级匹配:用 hasAnyAncestor(isDialog()) 或 hasAnyDescendant(...) 表达“在上方/下方某处”的关系,比多步遍历更简洁。

使用方式

将组合好的匹配器传给 onNode(matcher, useUnmergedTree=false) 或 onAllNodes(matcher, useUnmergedTree=false)。onNodeWithTag 等只是便捷包装,当谓词变复杂时应改用 onNode。

典型模式包括:用单个匹配器替换断言链(如 onNode(hasText("Submit") and hasClickAction() and isEnabled()).assertExists());层级匹配(如 onNode(hasText("Discard?") and hasAnyAncestor(isDialog())));取反与“恰好一个”(如 hasText("Submit") and isEnabled() and !isToggleable());自定义语义键匹配;缩小 TextField 查询范围(如 hasSetTextAction() and hasImeAction(ImeAction.Done))。

适用场景

需要查找“带 Submit 文本的已启用按钮”或任何满足多个约束的节点;需要组合 hasText 与 hasClickAction,或用 not 取反;需要按自定义 SemanticsPropertyKey 过滤;需要层级匹配器;以及希望把多个 assertIs* 调用合并为单个声明式谓词时。

限制与前置条件

测试类路径需包含 androidx.compose.ui:ui-test;需熟悉生产代码中的 Modifier.semantics { … },自定义键必须通过它设置才可被测试匹配。以下情况不适用:仅按 tag/文本/内容描述查找(应使用 finding-nodes-by-tag-text-content 技能);节点存在但需相对定位(应使用 traversing-the-semantics-tree 技能);匹配器没问题而只需调整断言(应参考 asserting-node-state-and-text 技能)。

强制规则:必须用 and/or/not 组合匹配器,不得在单个组合匹配器能表达同一意图时链式调用 assertIs*;自定义键必须用 expectValue 或 keyIsDefined 包装,不得在临时 lambda 中访问 node.config[CustomKey];关系为“上方/下方某处”时优先用 hasAnyAncestor/hasAnyDescendant;除非匹配器针对被合并折叠的内部元素,否则 useUnmergedTree 保持默认 false;不得重新定义已有预置匹配器;优先使用以 tag 为锚点的匹配器而非以文本为锚点。

验证方式:每个多条件查询使用单个 onNode(matcher);自定义键检查使用 expectValue/keyIsDefined;运行 ./gradlew :app:connectedDebugAndroidTest 或 :app:testDebugUnitTest 通过;故意破坏的匹配器失败信息能打印组合描述以定位失败子句;没有匹配器被同名局部 lambda 静默遮蔽。