mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
refactor(sidecar): drop iOS hierarchy helpers and their test
This commit is contained in:
1 parent
a9fdf49a62
commit
7c2f5e296c
2 files changed
-238
No files matched your search
@@ -772,115 +772,3 @@ private fun keyCodeToMaestro(adbKeyCode: String): maestro.KeyCode? {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XCUIElementType raw values that accept a tap. The XCTest hierarchy carries
|
|
||||||
// no clickable flag, so tappability is derived from the element type that
|
|
||||||
// XCUITest itself derives from the accessibility traits.
|
|
||||||
internal val IOS_CLICKABLE_ELEMENT_TYPES = setOf(
|
|
||||||
9, // button
|
|
||||||
10, // radioButton
|
|
||||||
12, // checkBox
|
|
||||||
14, // popUpButton
|
|
||||||
15, // comboBox
|
|
||||||
16, // menuButton
|
|
||||||
17, // toolbarButton
|
|
||||||
20, // key
|
|
||||||
37, // segmentedControl
|
|
||||||
40, // switch
|
|
||||||
41, // toggle
|
|
||||||
42, // link
|
|
||||||
54, // menuItem
|
|
||||||
75, // cell
|
|
||||||
79, // stepper
|
|
||||||
80, // tab
|
|
||||||
)
|
|
||||||
|
|
||||||
// XCUIElementType raw values that accept text input.
|
|
||||||
internal val IOS_EDITABLE_ELEMENT_TYPES = setOf(
|
|
||||||
45, // searchField
|
|
||||||
49, // textField
|
|
||||||
50, // secureTextField
|
|
||||||
52, // textView
|
|
||||||
)
|
|
||||||
|
|
||||||
// XCUIElementType raw values that scroll.
|
|
||||||
internal val IOS_SCROLLABLE_ELEMENT_TYPES = setOf(
|
|
||||||
26, // table
|
|
||||||
32, // collectionView
|
|
||||||
46, // scrollView
|
|
||||||
58, // webView
|
|
||||||
)
|
|
||||||
|
|
||||||
// Checkable element types and the value convention ("1" when on) mirror the
|
|
||||||
// upstream content-descriptor mapping.
|
|
||||||
internal val IOS_CHECKABLE_ELEMENT_TYPES = setOf(
|
|
||||||
12, // checkBox
|
|
||||||
40, // switch
|
|
||||||
41, // toggle
|
|
||||||
)
|
|
||||||
|
|
||||||
private val IOS_ELEMENT_TYPE_NAMES = arrayOf(
|
|
||||||
"Any", "Other", "Application", "Group", "Window", "Sheet", "Drawer",
|
|
||||||
"Alert", "Dialog", "Button", "RadioButton", "RadioGroup", "CheckBox",
|
|
||||||
"DisclosureTriangle", "PopUpButton", "ComboBox", "MenuButton",
|
|
||||||
"ToolbarButton", "Popover", "Keyboard", "Key", "NavigationBar", "TabBar",
|
|
||||||
"TabGroup", "Toolbar", "StatusBar", "Table", "TableRow", "TableColumn",
|
|
||||||
"Outline", "OutlineRow", "Browser", "CollectionView", "Slider",
|
|
||||||
"PageIndicator", "ProgressIndicator", "ActivityIndicator",
|
|
||||||
"SegmentedControl", "Picker", "PickerWheel", "Switch", "Toggle", "Link",
|
|
||||||
"Image", "Icon", "SearchField", "ScrollView", "ScrollBar", "StaticText",
|
|
||||||
"TextField", "SecureTextField", "DatePicker", "TextView", "Menu",
|
|
||||||
"MenuItem", "MenuBar", "MenuBarItem", "Map", "WebView", "IncrementArrow",
|
|
||||||
"DecrementArrow", "Timeline", "RatingIndicator", "ValueIndicator",
|
|
||||||
"SplitGroup", "Splitter", "RelevanceIndicator", "ColorWell", "HelpTag",
|
|
||||||
"Matte", "DockItem", "Ruler", "RulerMarker", "Grid", "LevelIndicator",
|
|
||||||
"Cell", "LayoutArea", "LayoutItem", "Handle", "Stepper", "Tab",
|
|
||||||
"TouchBar", "StatusItem",
|
|
||||||
)
|
|
||||||
|
|
||||||
internal fun iosElementTypeName(elementType: Int): String =
|
|
||||||
IOS_ELEMENT_TYPE_NAMES.getOrElse(elementType) { "Other" }
|
|
||||||
|
|
||||||
// iosAxElementToTreeNode maps the raw XCTest accessibility tree to the
|
|
||||||
// TreeNode JSON shape the runner parses. The upstream content-descriptor
|
|
||||||
// mapping drops the element type, leaving no way to tell buttons and text
|
|
||||||
// fields apart, so fuzz verbs found no tap or typing candidates on iOS.
|
|
||||||
internal fun iosAxElementToTreeNode(element: hierarchy.AXElement): Map<String, Any?> {
|
|
||||||
val elementType = element.elementType
|
|
||||||
val title = element.title.orEmpty()
|
|
||||||
val value = element.value.orEmpty()
|
|
||||||
val label = element.label.orEmpty()
|
|
||||||
val editable = elementType in IOS_EDITABLE_ELEMENT_TYPES
|
|
||||||
val checked = elementType in IOS_CHECKABLE_ELEMENT_TYPES && value == "1"
|
|
||||||
// text carries the element's visible string, matching what Android
|
|
||||||
// surfaces in its text attribute. Editable fields expose only their
|
|
||||||
// typed content: their label is the field caption (e.g. "Email"), and
|
|
||||||
// leaking it into text would make an empty field look filled. Static
|
|
||||||
// text and buttons carry the visible string in the label.
|
|
||||||
val text = if (editable) value else title.ifEmpty { value }.ifEmpty { label }
|
|
||||||
val attributes = linkedMapOf(
|
|
||||||
"accessibilityText" to label,
|
|
||||||
"title" to title,
|
|
||||||
"value" to value,
|
|
||||||
"text" to text,
|
|
||||||
"hintText" to element.placeholderValue.orEmpty(),
|
|
||||||
"resource-id" to element.identifier.orEmpty(),
|
|
||||||
"bounds" to element.frame.boundsString,
|
|
||||||
"class" to iosElementTypeName(elementType),
|
|
||||||
"enabled" to element.enabled.toString(),
|
|
||||||
"focused" to element.hasFocus.toString(),
|
|
||||||
"selected" to element.selected.toString(),
|
|
||||||
"checked" to checked.toString(),
|
|
||||||
"scrollable" to (elementType in IOS_SCROLLABLE_ELEMENT_TYPES).toString(),
|
|
||||||
)
|
|
||||||
return linkedMapOf(
|
|
||||||
"attributes" to attributes,
|
|
||||||
"children" to element.children.map { iosAxElementToTreeNode(it) },
|
|
||||||
"clickable" to (elementType in IOS_CLICKABLE_ELEMENT_TYPES),
|
|
||||||
"enabled" to element.enabled,
|
|
||||||
"focused" to element.hasFocus,
|
|
||||||
"checked" to checked,
|
|
||||||
"selected" to element.selected,
|
|
||||||
"editable" to editable,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package dev.sanderling.sidecar
|
|
||||||
|
|
||||||
import hierarchy.AXElement
|
|
||||||
import hierarchy.AXFrame
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class IosHierarchyTest {
|
|
||||||
|
|
||||||
private fun element(
|
|
||||||
elementType: Int,
|
|
||||||
label: String = "",
|
|
||||||
identifier: String = "",
|
|
||||||
title: String = "",
|
|
||||||
value: String = "",
|
|
||||||
placeholderValue: String = "",
|
|
||||||
children: ArrayList<AXElement> = arrayListOf(),
|
|
||||||
) = AXElement(
|
|
||||||
label,
|
|
||||||
elementType,
|
|
||||||
identifier,
|
|
||||||
0,
|
|
||||||
0L,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
placeholderValue,
|
|
||||||
value,
|
|
||||||
AXFrame(10f, 20f, 100f, 50f),
|
|
||||||
true,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun buttonIsClickableNotEditable() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 9, label = "Sign in", identifier = "LoginSubmit"))
|
|
||||||
assertEquals(true, node["clickable"])
|
|
||||||
assertEquals(false, node["editable"])
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("Button", attributes["class"])
|
|
||||||
assertEquals("LoginSubmit", attributes["resource-id"])
|
|
||||||
assertEquals("Sign in", attributes["accessibilityText"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun textFieldIsEditableNotClickable() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 49, value = "[email protected]"))
|
|
||||||
assertEquals(false, node["clickable"])
|
|
||||||
assertEquals(true, node["editable"])
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("TextField", attributes["class"])
|
|
||||||
assertEquals("[email protected]", attributes["text"])
|
|
||||||
assertEquals("[email protected]", attributes["value"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun titleWinsOverValueForText() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 48, title = "Heading", value = "ignored"))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("Heading", attributes["text"])
|
|
||||||
assertEquals("StaticText", attributes["class"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun staticTextFallsBackToLabelForText() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 48, label = "$1,234.00"))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("$1,234.00", attributes["text"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun buttonFallsBackToLabelForText() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 9, label = "Sign in"))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("Sign in", attributes["text"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun emptyTextFieldDoesNotLeakLabelIntoText() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 49, label = "Email"))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("", attributes["text"])
|
|
||||||
assertEquals("Email", attributes["accessibilityText"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun switchOnIsChecked() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 40, value = "1"))
|
|
||||||
assertEquals(true, node["checked"])
|
|
||||||
assertEquals(true, node["clickable"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scrollViewIsScrollable() {
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 46))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val attributes = node["attributes"] as Map<String, String>
|
|
||||||
assertEquals("true", attributes["scrollable"])
|
|
||||||
assertEquals("ScrollView", attributes["class"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun childrenAreMappedRecursively() {
|
|
||||||
val child = element(elementType = 48, title = "Inner")
|
|
||||||
val node = iosAxElementToTreeNode(element(elementType = 1, children = arrayListOf(child)))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val children = node["children"] as List<Map<String, Any?>>
|
|
||||||
assertEquals(1, children.size)
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val childAttributes = children[0]["attributes"] as Map<String, String>
|
|
||||||
assertEquals("Inner", childAttributes["text"])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun unknownElementTypeFallsBackToOther() {
|
|
||||||
assertEquals("Other", iosElementTypeName(999))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user