commands/FBXCTestCommands.py (969 lines of code) (raw):
#!/usr/bin/python
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import re
import fbchisellldbbase as fb
import lldb
NOT_FOUND = 0xFFFFFFFF # UINT32_MAX
def lldbcommands():
return [FBXCPrintDebugDescription(), FBXCPrintTree(), FBXCPrintObject(), FBXCNoId()]
class FBXCPrintDebugDescription(fb.FBCommand):
def name(self):
return "xdebug"
def description(self):
return "Print debug description the XCUIElement in human readable format."
def args(self):
return [
fb.FBCommandArgument(
arg="element",
type="XCUIElement*",
help="The element to print debug description.",
default="__default__",
)
]
def run(self, arguments, options):
element = arguments[0]
language = fb.currentLanguage()
if element == "__default__":
element = (
"XCUIApplication()"
if language == lldb.eLanguageTypeSwift
else "(XCUIApplication *)[[XCUIApplication alloc] init]"
)
if language == lldb.eLanguageTypeSwift:
print(
fb.evaluateExpressionValue(
"{}.debugDescription".format(element), language=language
)
.GetObjectDescription()
.replace("\\n", "\n")
.replace("\\'", "'")
.strip(' "\n\t')
)
else:
print(
fb.evaluateExpressionValue(
"[{} debugDescription]".format(element)
).GetObjectDescription()
)
class FBXCPrintTree(fb.FBCommand):
def name(self):
return "xtree"
def description(self):
return "Print XCUIElement subtree."
def args(self):
return [
fb.FBCommandArgument(
arg="element",
type="XCUIElement*",
help="The element to print tree.",
default="__default__",
)
]
def options(self):
return [
fb.FBCommandArgument(
arg="pointer",
short="-p",
long="--pointer",
type="BOOL",
boolean=True,
default=False,
help="Print pointers",
),
fb.FBCommandArgument(
arg="trait",
short="-t",
long="--traits",
type="BOOL",
boolean=True,
default=False,
help="Print traits",
),
fb.FBCommandArgument(
arg="frame",
short="-f",
long="--frame",
type="BOOL",
boolean=True,
default=False,
help="Print frames",
),
]
def run(self, arguments, options):
element = arguments[0]
language = fb.currentLanguage()
if element == "__default__":
element = (
"XCUIApplication()"
if language == lldb.eLanguageTypeSwift
else "(XCUIApplication *)[[XCUIApplication alloc] init]"
)
# Evaluate object
element_sbvalue = fb.evaluateExpressionValue(
"{}".format(element), language=language
)
""":type: lldb.SBValue"""
# Get pointer value, so it will be working in Swift and Objective-C
element_pointer = int(element_sbvalue.GetValue(), 16)
# Get XCElementSnapshot object
snapshot = take_snapshot(element_pointer)
# Print tree for snapshot element
snapshot_object = XCElementSnapshot(snapshot, language=language)
print(
snapshot_object.tree().hierarchy_text(
pointer=options.pointer, trait=options.trait, frame=options.frame
)
)
class FBXCPrintObject(fb.FBCommand):
def name(self):
return "xobject"
def description(self):
return "Print XCUIElement details."
def args(self):
return [
fb.FBCommandArgument(
arg="element",
type="XCUIElement*",
help="The element to print details.",
default="__default__",
)
]
def run(self, arguments, options):
element = arguments[0]
language = fb.currentLanguage()
if element == "__default__":
element = (
"XCUIApplication()"
if language == lldb.eLanguageTypeSwift
else "(XCUIApplication *)[[XCUIApplication alloc] init]"
)
# Evaluate object
element_sbvalue = fb.evaluateExpressionValue(
"{}".format(element), language=language
)
""":type: lldb.SBValue"""
# Get pointer value, so it will be working in Swift and Objective-C
element_pointer = int(element_sbvalue.GetValue(), 16)
# Get XCElementSnapshot object
snapshot = take_snapshot(element_pointer)
# Print details of snapshot element
snapshot_object = XCElementSnapshot(snapshot, language=language)
print(snapshot_object.detail_summary())
class FBXCNoId(fb.FBCommand):
def name(self):
return "xnoid"
def description(self):
return "Print XCUIElement objects with label but without identifier."
def args(self):
return [
fb.FBCommandArgument(
arg="element",
type="XCUIElement*",
help="The element from start to.",
default="__default__",
)
]
def options(self):
return [
fb.FBCommandArgument(
arg="status_bar",
short="-s",
long="--status-bar",
type="BOOL",
boolean=True,
default=False,
help="Print status bar items",
),
fb.FBCommandArgument(
arg="pointer",
short="-p",
long="--pointer",
type="BOOL",
boolean=True,
default=False,
help="Print pointers",
),
fb.FBCommandArgument(
arg="trait",
short="-t",
long="--traits",
type="BOOL",
boolean=True,
default=False,
help="Print traits",
),
fb.FBCommandArgument(
arg="frame",
short="-f",
long="--frame",
type="BOOL",
boolean=True,
default=False,
help="Print frames",
),
]
def run(self, arguments, options):
element = arguments[0]
language = fb.currentLanguage()
if element == "__default__":
element = (
"XCUIApplication()"
if language == lldb.eLanguageTypeSwift
else "(XCUIApplication *)[[XCUIApplication alloc] init]"
)
# Evaluate object
element_sbvalue = fb.evaluateExpressionValue(
"{}".format(element), language=language
)
""":type: lldb.SBValue"""
# Get pointer value, so it will be working in Swift and Objective-C
element_pointer = int(element_sbvalue.GetValue(), 16)
# Get XCElementSnapshot object
snapshot = take_snapshot(element_pointer)
# Print tree for snapshot element
snapshot_object = XCElementSnapshot(snapshot, language=language)
elements = snapshot_object.find_missing_identifiers(
status_bar=options.status_bar
)
if elements is not None:
print(
elements.hierarchy_text(
pointer=options.pointer, trait=options.trait, frame=options.frame
)
)
else:
print("Couldn't found elements without identifier")
def take_snapshot(element):
"""
Takes snapshot (XCElementSnapshot) from XCUIElement (as pointer)
:param int element: Pointer to the XCUIElement
:return: XCElementSnapshot object
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(XCElementSnapshot *)[[[{} query] matchingSnapshotsWithError:nil] firstObject]".format(
element
)
)
class _ElementList(object):
"""
Store element and list of children
:param XCElementSnapshot element: XCElementSnapshot
:param list[_ElementList] children: List of XCElementSnapshot objects
"""
def __init__(self, element, children):
self.element = element
self.children = children
def text(self, pointer, trait, frame, indent):
"""
String representation of the element
:param bool pointer: Print pointers
:param bool trait: Print traits
:param bool frame: Print frames
:param int indent: Indention
:return: String representation of the element
:rtype: str
"""
indent_string = " | " * indent
return "{}{}\n".format(
indent_string,
self.element.summary(pointer=pointer, trait=trait, frame=frame),
)
def hierarchy_text(self, pointer=False, trait=False, frame=False, indent=0):
"""
String representation of the hierarchy of elements
:param bool pointer: Print pointers
:param bool trait: Print traits
:param bool frame: Print frames
:param int indent: Indention
:return: String representation of the hierarchy of elements
:rtype: str
"""
s = self.text(pointer=pointer, trait=trait, frame=frame, indent=indent)
for e in self.children:
s += e.hierarchy_text(
pointer=pointer, trait=trait, frame=frame, indent=indent + 1
)
return s
class XCElementSnapshot(object):
"""
XCElementSnapshot wrapper
:param lldb.SBValue element: XCElementSnapshot object
:param str element_value: Pointer to XCElementSnapshot object
:param language: Project language
:param lldb.SBValue _type: XCUIElement type / XCUIElementType
:param lldb.SBValue _traits: UIAccessibilityTraits
:param lldb.SBValue | None _frame: XCUIElement frame
:param lldb.SBValue _identifier: XCUIElement identifier
:param lldb.SBValue _value: XCUIElement value
:param lldb.SBValue _placeholderValue: XCUIElement placeholder value
:param lldb.SBValue _label: XCUIElement label
:param lldb.SBValue _title: XCUIElement title
:param lldb.SBValue _children: XCUIElement children
:param lldb.SBValue _enabled: XCUIElement is enabled
:param lldb.SBValue _selected: XCUIElement is selected
:param lldb.SBValue _isMainWindow: XCUIElement is main window
:param lldb.SBValue _hasKeyboardFocus: XCUIElement has keyboard focus
:param lldb.SBValue _hasFocus: XCUIElement has focus
:param lldb.SBValue _generation: XCUIElement generation
:param lldb.SBValue _horizontalSizeClass: XCUIElement horizontal class
:param lldb.SBValue _verticalSizeClass: XCUIElement vertical class
"""
def __init__(self, element, language):
"""
:param lldb.SBValue element: XCElementSnapshot object
:param language: Project language
"""
super(XCElementSnapshot, self).__init__()
self.element = element
self.element_value = self.element.GetValue()
self.language = language
self._type = None
self._traits = None
self._frame = None
self._identifier = None
self._value = None
self._placeholderValue = None
self._label = None
self._title = None
self._children = None
self._enabled = None
self._selected = None
self._isMainWindow = None
self._hasKeyboardFocus = None
self._hasFocus = None
self._generation = None
self._horizontalSizeClass = None
self._verticalSizeClass = None
@property
def is_missing_identifier(self):
"""
Checks if element has a label but doesn't have an identifier.
:return: True if element has a label but doesn't have an identifier.
:rtype: bool
"""
return len(self.identifier_value) == 0 and len(self.label_value) > 0
@property
def type(self):
"""
:return: XCUIElement type / XCUIElementType
:rtype: lldb.SBValue
"""
if self._type is None:
name = "_elementType"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._type = fb.evaluateExpressionValue(
"(int)[{} elementType]".format(self.element_value)
)
else:
self._type = self.element.GetChildMemberWithName(name)
return self._type
@property
def type_value(self):
"""
:return: XCUIElementType value
:rtype: int
"""
return int(self.type.GetValue())
@property
def type_summary(self):
"""
:return: XCUIElementType summary
:rtype: str
"""
return self.get_type_value_string(self.type_value)
@property
def traits(self):
"""
:return: UIAccessibilityTraits
:rtype: lldb.SBValue
"""
if self._traits is None:
name = "_traits"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._traits = fb.evaluateExpressionValue(
"(int)[{} traits]".format(self.element_value)
)
else:
self._traits = self.element.GetChildMemberWithName(name)
return self._traits
@property
def traits_value(self):
"""
:return: UIAccessibilityTraits value
:rtype: int
"""
return int(self.traits.GetValue())
@property
def traits_summary(self):
"""
:return: UIAccessibilityTraits summary
:rtype: str
"""
return self.get_traits_value_string(self.traits_value)
@property
def frame(self):
"""
:return: XCUIElement frame
:rtype: lldb.SBValue
"""
if self._frame is None:
import_uikit()
name = "_frame"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._frame = fb.evaluateExpressionValue(
"(CGRect)[{} frame]".format(self.element_value)
)
else:
self._frame = self.element.GetChildMemberWithName(name)
return self._frame
@property
def frame_summary(self):
"""
:return: XCUIElement frame summary
:rtype: str
"""
return CGRect(self.frame).summary()
@property
def identifier(self):
"""
:return: XCUIElement identifier
:rtype: lldb.SBValue
"""
if self._identifier is None:
name = "_identifier"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._identifier = fb.evaluateExpressionValue(
"(NSString *)[{} identifier]".format(self.element_value)
)
else:
self._identifier = self.element.GetChildMemberWithName(name)
return self._identifier
@property
def identifier_value(self):
"""
:return: XCUIElement identifier value
:rtype: str
"""
return normalize_summary(self.identifier.GetSummary())
@property
def identifier_summary(self):
"""
:return: XCUIElement identifier summary
:rtype: str | None
"""
if len(self.identifier_value) == 0:
return None
return "identifier: '{}'".format(self.identifier_value)
@property
def value(self):
"""
:return: XCUIElement value
:rtype: lldb.SBValue
"""
if self._value is None:
name = "_value"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._value = fb.evaluateExpressionValue(
"(NSString *)[{} value]".format(self.element_value)
)
else:
self._value = self.element.GetChildMemberWithName(name)
return self._value
@property
def value_value(self):
"""
:return: XCUIElement value value
:rtype: str
"""
return normalize_summary(self.value.GetSummary())
@property
def value_summary(self):
"""
:return: XCUIElement value summary
:rtype: str | None
"""
if len(self.value_value) == 0:
return None
return "value: '{}'".format(self.value_value)
@property
def placeholder(self):
"""
:return: XCUIElement placeholder value
:rtype: lldb.SBValue
"""
if self._placeholderValue is None:
name = "_placeholderValue"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._placeholderValue = fb.evaluateExpressionValue(
"(NSString *)[{} placeholderValue]".format(self.element_value)
)
else:
self._placeholderValue = self.element.GetChildMemberWithName(name)
return self._placeholderValue
@property
def placeholder_value(self):
"""
:return: XCUIElement placeholderValue value
:rtype: str
"""
return normalize_summary(self.placeholder.GetSummary())
@property
def placeholder_summary(self):
"""
:return: XCUIElement placeholderValue summary
:rtype: str | None
"""
if len(self.placeholder_value) == 0:
return None
return "placeholderValue: '{}'".format(self.placeholder_value)
@property
def label(self):
"""
:return: XCUIElement label
:rtype: lldb.SBValue
"""
if self._label is None:
name = "_label"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._label = fb.evaluateExpressionValue(
"(NSString *)[{} label]".format(self.element_value)
)
else:
self._label = self.element.GetChildMemberWithName(name)
return self._label
@property
def label_value(self):
"""
:return: XCUIElement label value
:rtype: str
"""
return normalize_summary(self.label.GetSummary())
@property
def label_summary(self):
"""
:return: XCUIElement label summary
:rtype: str | None
"""
if len(self.label_value) == 0:
return None
return "label: '{}'".format(self.label_value)
@property
def title(self):
"""
:return: XCUIElement title
:rtype: lldb.SBValue
"""
if self._title is None:
name = "_title"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._title = fb.evaluateExpressionValue(
"(NSString *)[{} title]".format(self.element_value)
)
else:
self._title = self.element.GetChildMemberWithName(name)
return self._title
@property
def title_value(self):
"""
:return: XCUIElement title value
:rtype: str
"""
return normalize_summary(self.title.GetSummary())
@property
def title_summary(self):
"""
:return: XCUIElement title summary
:rtype: str | None
"""
if len(self.title_value) == 0:
return None
return "title: '{}'".format(self.title_value)
@property
def children(self):
"""
:return: XCUIElement children
:rtype: lldb.SBValue
"""
if self._children is None:
name = "_children"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._children = fb.evaluateExpressionValue(
"(NSArray *)[{} children]".format(self.element_value)
)
else:
self._children = self.element.GetChildMemberWithName(name)
return self._children
@property
def children_count(self):
"""
:return: XCUIElement children count
:rtype: int
"""
return self.children.GetNumChildren()
@property
def children_list(self):
"""
:return: XCUIElement children list
:rtype: list[lldb.SBValue]
"""
return [self.children.GetChildAtIndex(i) for i in range(self.children_count)]
@property
def enabled(self):
"""
:return: XCUIElement is enabled
:rtype: lldb.SBValue
"""
if self._enabled is None:
name = "_enabled"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._enabled = fb.evaluateExpressionValue(
"(BOOL)[{} enabled]".format(self.element_value)
)
else:
self._enabled = self.element.GetChildMemberWithName(name)
return self._enabled
@property
def enabled_value(self):
"""
:return: XCUIElement is enabled value
:rtype: bool
"""
return bool(self.enabled.GetValueAsSigned())
@property
def enabled_summary(self):
"""
:return: XCUIElement is enabled summary
:rtype: str | None
"""
if not self.enabled_value:
return "enabled: {}".format(self.enabled_value)
return None
@property
def selected(self):
"""
:return: XCUIElement is selected
:rtype: lldb.SBValue
"""
if self._selected is None:
name = "_selected"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._selected = fb.evaluateExpressionValue(
"(BOOL)[{} selected]".format(self.element_value)
)
else:
self._selected = self.element.GetChildMemberWithName(name)
return self._selected
@property
def selected_value(self):
"""
:return: XCUIElement is selected value
:rtype: bool
"""
return bool(self.selected.GetValueAsSigned())
@property
def selected_summary(self):
"""
:return: XCUIElement is selected summary
:rtype: str | None
"""
if self.selected_value:
return "selected: {}".format(self.selected_value)
return None
@property
def is_main_window(self):
"""
:return: XCUIElement isMainWindow
:rtype: lldb.SBValue
"""
if self._isMainWindow is None:
name = "_isMainWindow"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._isMainWindow = fb.evaluateExpressionValue(
"(BOOL)[{} isMainWindow]".format(self.element_value)
)
else:
self._isMainWindow = self.element.GetChildMemberWithName(name)
return self._isMainWindow
@property
def is_main_window_value(self):
"""
:return: XCUIElement isMainWindow value
:rtype: bool
"""
return bool(self.is_main_window.GetValueAsSigned())
@property
def is_main_window_summary(self):
"""
:return: XCUIElement isMainWindow summary
:rtype: str | None
"""
if self.is_main_window_value:
return "MainWindow"
return None
@property
def keyboard_focus(self):
"""
:return: XCUIElement hasKeyboardFocus
:rtype: lldb.SBValue
"""
if self._hasKeyboardFocus is None:
name = "_hasKeyboardFocus"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._hasKeyboardFocus = fb.evaluateExpressionValue(
"(BOOL)[{} hasKeyboardFocus]".format(self.element_value)
)
else:
self._hasKeyboardFocus = self.element.GetChildMemberWithName(name)
return self._hasKeyboardFocus
@property
def keyboard_focus_value(self):
"""
:return: XCUIElement hasKeyboardFocus value
:rtype: bool
"""
return bool(self.keyboard_focus.GetValueAsSigned())
@property
def keyboard_focus_summary(self):
"""
:return: XCUIElement hasKeyboardFocus summary
:rtype: str | None
"""
if self.keyboard_focus_value:
return "hasKeyboardFocus: {}".format(self.keyboard_focus_value)
return None
@property
def focus(self):
"""
:return: XCUIElement hasFocus
:rtype: lldb.SBValue
"""
if self._hasFocus is None:
name = "_hasFocus"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._hasFocus = fb.evaluateExpressionValue(
"(BOOL)[{} hasFocus]".format(self.element_value)
)
else:
self._hasFocus = self.element.GetChildMemberWithName(name)
return self._hasFocus
@property
def focus_value(self):
"""
:return: XCUIElement hasFocus value
:rtype: bool
"""
return bool(self.focus.GetValueAsSigned())
@property
def focus_summary(self):
"""
:return: XCUIElement hasFocus summary
:rtype: str | None
"""
if self.focus_value:
return "hasFocus: {}".format(self.focus_value)
return None
@property
def generation(self):
"""
:return: XCUIElement generation
:rtype: lldb.SBValue
"""
if self._generation is None:
name = "_generation"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._generation = fb.evaluateExpressionValue(
"(unsigned int)[{} generation]".format(self.element_value)
)
else:
self._generation = self.element.GetChildMemberWithName(name)
return self._generation
@property
def generation_value(self):
"""
:return: XCUIElement generation value
:rtype: int
"""
return int(self.generation.GetValueAsUnsigned())
@property
def horizontal_size_class(self):
"""
:return: XCUIElement horizontal size class
:rtype: lldb.SBValue
"""
if self._horizontalSizeClass is None:
name = "_horizontalSizeClass"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._horizontalSizeClass = fb.evaluateExpressionValue(
"(int)[{} horizontalSizeClass]".format(self.element_value)
)
else:
self._horizontalSizeClass = self.element.GetChildMemberWithName(name)
return self._horizontalSizeClass
@property
def horizontal_size_class_value(self):
"""
:return: XCUIElement horizontal size class value
:rtype: int
"""
return int(self.horizontal_size_class.GetValue())
@property
def horizontal_size_class_summary(self):
"""
:return: XCUIElement horizontal size class summary
"""
return self.get_user_interface_size_class_string(
self.horizontal_size_class_value
)
@property
def vertical_size_class(self):
"""
:return: XCUIElement vertical size class
:rtype: lldb.SBValue
"""
if self._verticalSizeClass is None:
name = "_verticalSizeClass"
if self.element.GetIndexOfChildWithName(name) == NOT_FOUND:
self._verticalSizeClass = fb.evaluateExpressionValue(
"(int)[{} verticalSizeClass]".format(self.element_value)
)
else:
self._verticalSizeClass = self.element.GetChildMemberWithName(name)
return self._verticalSizeClass
@property
def vertical_size_class_value(self):
"""
:return: XCUIElement vertical size class value
:rtype: int
"""
return int(self.vertical_size_class.GetValue())
@property
def vertical_size_class_summary(self):
"""
:return: XCUIElement vertical size class summary
"""
return self.get_user_interface_size_class_string(self.vertical_size_class_value)
@property
def uniquely_identifying_objective_c_code(self):
"""
:return: XCUIElement uniquely identifying Objective-C code
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(id)[{} _uniquelyIdentifyingObjectiveCCode]".format(self.element_value)
)
@property
def uniquely_identifying_objective_c_code_value(self):
"""
:return: XCUIElement uniquely identifying Objective-C code value
:rtype: str
"""
return normalize_array_description(
self.uniquely_identifying_objective_c_code.GetObjectDescription()
)
@property
def uniquely_identifying_swift_code(self):
"""
:return: XCUIElement uniquely identifying Swift code
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(id)[{} _uniquelyIdentifyingSwiftCode]".format(self.element_value)
)
@property
def uniquely_identifying_swift_code_value(self):
"""
:return: XCUIElement uniquely identifying Swift code value
:rtype: str
"""
return normalize_array_description(
self.uniquely_identifying_swift_code.GetObjectDescription()
)
@property
def is_touch_bar_element(self):
"""
:return: XCUIElement is touch bar element
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(BOOL)[{} isTouchBarElement]".format(self.element_value)
)
@property
def is_touch_bar_element_value(self):
"""
:return: XCUIElement is touch bar element value
:rtype: bool
"""
return bool(self.is_touch_bar_element.GetValueAsSigned())
@property
def is_top_level_touch_bar_element(self):
"""
:return: XCUIElement is top level touch bar element
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(BOOL)[{} isTopLevelTouchBarElement]".format(self.element_value)
)
@property
def is_top_level_touch_bar_element_value(self):
"""
:return: XCUIElement is top level touch bar element value
:rtype: bool
"""
return bool(self.is_top_level_touch_bar_element.GetValueAsSigned())
@property
def suggested_hit_points(self):
"""
:return: XCUIElement suggested hit points
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue(
"(NSArray *)[{} suggestedHitpoints]".format(self.element_value)
)
@property
def suggested_hit_points_value(self):
"""
:return: XCUIElement suggested hit points
:rtype: str
"""
return normalize_array_description(
self.suggested_hit_points.GetObjectDescription()
)
@property
def visible_frame(self):
"""
:return: XCUIElement visible frame
:rtype: lldb.SBValue
"""
import_uikit()
return fb.evaluateExpressionValue(
"(CGRect)[{} visibleFrame]".format(self.element_value)
)
@property
def visible_frame_summary(self):
"""
:return: XCUIElement visible frame
:rtype: str
"""
return CGRect(self.visible_frame).summary()
@property
def depth(self):
"""
:return: XCUIElement depth
:rtype: lldb.SBValue
"""
return fb.evaluateExpressionValue("(int)[{} depth]".format(self.element_value))
@property
def depth_value(self):
"""
:return: XCUIElement depth
:rtype: int
"""
return int(self.depth.GetValue())
@property
def hit_point(self):
"""
:return: XCUIElement hit point
:rtype: lldb.SBValue
"""
import_uikit()
return fb.evaluateExpressionValue(
"(CGPoint)[{} hitPoint]".format(self.element_value)
)
@property
def hit_point_value(self):
"""
:return: XCUIElement hit point
:rtype: str
"""
return CGPoint(self.hit_point).summary()
@property
def hit_point_for_scrolling(self):
"""
:return: XCUIElement hit point for scrolling
:rtype: lldb.SBValue
"""
import_uikit()
return fb.evaluateExpressionValue(
"(CGPoint)[{} hitPointForScrolling]".format(self.element_value)
)
@property
def hit_point_for_scrolling_value(self):
"""
:return: XCUIElement hit point for scrolling
:rtype: str
"""
return CGPoint(self.hit_point_for_scrolling).summary()
def summary(self, pointer=False, trait=False, frame=False):
"""
Returns XCElementSnapshot summary
:param bool pointer: Print pointers
:param bool trait: Print traits
:param bool frame: Print frames
:return: XCElementSnapshot summary
:rtype: str
"""
type_text = self.type_summary
if pointer:
type_text += " {:#x}".format(int(self.element_value, 16))
if trait:
type_text += " traits: {}({:#x})".format(
self.traits_summary, self.traits_value
)
frame_text = self.frame_summary if frame else None
identifier = self.identifier_summary
label = self.label_summary
title = self.title_summary
value = self.value_summary
placeholder = self.placeholder_summary
enabled = self.enabled_summary
selected = self.selected_summary
main_window = self.is_main_window_summary
keyboard_focus = self.keyboard_focus_summary
focus = self.focus_summary
texts = [
t
for t in [
frame_text,
identifier,
label,
title,
value,
placeholder,
enabled,
selected,
main_window,
keyboard_focus,
focus,
]
if t is not None
]
return "{}: {}".format(type_text, ", ".join(texts))
def detail_summary(self):
"""
Returns XCElementSnapshot detail summary
:return: XCElementSnapshot detail summary
:rtype: str
"""
texts = list()
texts.append("Pointer: {:#x}".format(int(self.element_value, 16)))
texts.append("Type: {}".format(self.type_summary))
texts.append("Depth: {}".format(self.depth_value))
texts.append(
"Traits: {} ({:#x})".format(self.traits_summary, self.traits_value)
)
texts.append("Frame: {}".format(self.frame_summary))
texts.append("Visible frame: {}".format(self.visible_frame_summary))
texts.append("Identifier: '{}'".format(self.identifier_value))
texts.append("Label: '{}'".format(self.label_value))
texts.append("Title: '{}'".format(self.title_value))
texts.append("Value: '{}'".format(self.value_value))
texts.append("Placeholder: '{}'".format(self.placeholder_value))
if self.language != lldb.eLanguageTypeSwift:
# They doesn't work on Swift :(
texts.append("Hit point: {}".format(self.hit_point_value))
texts.append(
"Hit point for scrolling: {}".format(self.hit_point_for_scrolling_value)
)
texts.append("Enabled: {}".format(self.enabled_value))
texts.append("Selected: {}".format(self.selected_value))
texts.append("Main Window: {}".format(self.is_main_window_value))
texts.append("Keyboard focus: {}".format(self.keyboard_focus_value))
texts.append("Focus: {}".format(self.focus_value))
texts.append("Generation: {}".format(self.generation_value))
texts.append(
"Horizontal size class: {}".format(self.horizontal_size_class_summary)
)
texts.append("Vertical size class: {}".format(self.vertical_size_class_summary))
texts.append("TouchBar element: {}".format(self.is_touch_bar_element_value))
texts.append(
"TouchBar top level element: {}".format(
self.is_top_level_touch_bar_element_value
)
)
texts.append(
"Unique Objective-C: {}".format(
self.uniquely_identifying_objective_c_code_value
)
)
texts.append(
"Unique Swift: {}".format(self.uniquely_identifying_swift_code_value)
)
texts.append("Suggested hit points: {}".format(self.suggested_hit_points_value))
return "\n".join(texts)
def tree(self):
"""
Returns tree of elements in hierarchy
:return: Elements hierarchy
:rtype: _ElementList
"""
children = [
XCElementSnapshot(e, self.language).tree() for e in self.children_list
]
return _ElementList(self, children)
def find_missing_identifiers(self, status_bar):
"""
Find element which has a label but doesn't have an identifier
:param bool status_bar: Print status bar items
:return: Hierarchy structure with items which has a label but doesn't have an identifier
:rtype: _ElementList | None
"""
# Do not print status bar items
if status_bar is not True and self.type_value == XCUIElementType.StatusBar:
return None
children_missing = [
XCElementSnapshot(e, self.language).find_missing_identifiers(
status_bar=status_bar
)
for e in self.children_list
]
children_missing = [x for x in children_missing if x is not None]
# Self and its children are not missing identifiers
if self.is_missing_identifier is False and len(children_missing) == 0:
return None
return _ElementList(self, children_missing)
@staticmethod
def get_type_value_string(value):
"""
Get element type string from XCUIElementType (as int)
:param int value: XCUIElementType (as int)
:return: XCUIElementType string
:rtype: str
"""
return XCUIElementType.name_for_value(value)
@staticmethod
def get_traits_value_string(value):
"""
Get element traits string from UIAccessibilityTraits (as int)
:param int value: UIAccessibilityTraits (as int)
:return: UIAccessibilityTraits string
:rtype: str
"""
return UIAccessibilityTraits.name_for_value(value)
@staticmethod
def get_user_interface_size_class_string(value):
"""
Get user interface size class string from UIUserInterfaceSizeClass (as int)
:param value: UIAccessibilityTraits (as int)
:return: UIUserInterfaceSizeClass string
:rtype: str
"""
return UIUserInterfaceSizeClass.name_for_value(value)
class XCUIElementType(object):
"""
Represents all XCUIElementType types
"""
Any = 0
Other = 1
Application = 2
Group = 3
Window = 4
Sheet = 5
Drawer = 6
Alert = 7
Dialog = 8
Button = 9
RadioButton = 10
RadioGroup = 11
CheckBox = 12
DisclosureTriangle = 13
PopUpButton = 14
ComboBox = 15
MenuButton = 16
ToolbarButton = 17
Popover = 18
Keyboard = 19
Key = 20
NavigationBar = 21
TabBar = 22
TabGroup = 23
Toolbar = 24
StatusBar = 25
Table = 26
TableRow = 27
TableColumn = 28
Outline = 29
OutlineRow = 30
Browser = 31
CollectionView = 32
Slider = 33
PageIndicator = 34
ProgressIndicator = 35
ActivityIndicator = 36
SegmentedControl = 37
Picker = 38
PickerWheel = 39
Switch = 40
Toggle = 41
Link = 42
Image = 43
Icon = 44
SearchField = 45
ScrollView = 46
ScrollBar = 47
StaticText = 48
TextField = 49
SecureTextField = 50
DatePicker = 51
TextView = 52
Menu = 53
MenuItem = 54
MenuBar = 55
MenuBarItem = 56
Map = 57
WebView = 58
IncrementArrow = 59
DecrementArrow = 60
Timeline = 61
RatingIndicator = 62
ValueIndicator = 63
SplitGroup = 64
Splitter = 65
RelevanceIndicator = 66
ColorWell = 67
HelpTag = 68
Matte = 69
DockItem = 70
Ruler = 71
RulerMarker = 72
Grid = 73
LevelIndicator = 74
Cell = 75
LayoutArea = 76
LayoutItem = 77
Handle = 78
Stepper = 79
Tab = 80
TouchBar = 81
@classmethod
def _attributes_by_value(cls):
"""
:return: Hash of all attributes and their values
:rtype: dict[int, str]
"""
class_attributes = set(dir(cls)) - set(dir(object))
return dict(
[
(getattr(cls, n), n)
for n in class_attributes
if not callable(getattr(cls, n)) and not n.startswith("__")
]
)
@classmethod
def name_for_value(cls, value):
"""
Get element type string from XCUIElementType (as int)
:param int value: XCUIElementType (as int)
:return: Name of type
:rtype: str
"""
attributes = cls._attributes_by_value()
if value in attributes:
return attributes[value]
else:
return "Unknown ({:#x})".format(value)
class UIAccessibilityTraits(object):
"""
Represents all UIAccessibilityTraits types
"""
Button = 0x0000000000000001
Link = 0x0000000000000002
Image = 0x0000000000000004
Selected = 0x0000000000000008
PlaysSound = 0x0000000000000010
KeyboardKey = 0x0000000000000020
StaticText = 0x0000000000000040
SummaryElement = 0x0000000000000080
NotEnabled = 0x0000000000000100
UpdatesFrequently = 0x0000000000000200
SearchField = 0x0000000000000400
StartsMediaSession = 0x0000000000000800
Adjustable = 0x0000000000001000
AllowsDirectInteraction = 0x0000000000002000
CausesPageTurn = 0x0000000000004000
TabBar = 0x0000000000008000
Header = 0x0000000000010000
@classmethod
def _attributes_by_value(cls):
"""
:return: Hash of all attributes and their values
:rtype: dict[int, str]
"""
class_attributes = set(dir(cls)) - set(dir(object))
return dict(
[
(getattr(cls, n), n)
for n in class_attributes
if not callable(getattr(cls, n)) and not n.startswith("__")
]
)
@classmethod
def name_for_value(cls, value):
"""
Get element traits string from UIAccessibilityTraits (as int)
:param int value: UIAccessibilityTraits (as int)
:return: UIAccessibilityTraits string
:rtype: str
"""
if value == 0:
return "None"
traits = []
attributes = cls._attributes_by_value()
for k in attributes.keys():
if value & k:
traits.append(attributes[k])
if len(traits) == 0:
return "Unknown"
else:
return ", ".join(traits)
class UIUserInterfaceSizeClass(object):
"""
Represents all UIUserInterfaceSizeClass types
"""
Unspecified = 0
Compact = 1
Regular = 2
@classmethod
def name_for_value(cls, value):
"""
Get user interface size class string from UIUserInterfaceSizeClass (as int)
:param int value: UIAccessibilityTraits (as int)
:return: UIUserInterfaceSizeClass string
:rtype: str
"""
if value == cls.Unspecified:
return "Unspecified"
elif value == cls.Compact:
return "Compact"
elif value == cls.Regular:
return "Regular"
else:
return "Unknown ({:#x})".format(value)
class CGRect(object):
"""
CGRect wrapper
:param lldb.SBValue element: CGRect object
"""
def __init__(self, element):
"""
:param lldb.SBValue element: CGRect object
"""
super(CGRect, self).__init__()
self.element = element
def summary(self):
"""
:return: CGRect summary
:rtype: str
"""
origin_element = self.element.GetChildMemberWithName("origin")
origin = CGPoint(origin_element)
size = self.element.GetChildMemberWithName("size")
width = size.GetChildMemberWithName("width")
height = size.GetChildMemberWithName("height")
width_value = float(width.GetValue())
height_value = float(height.GetValue())
return "{{{}, {{{}, {}}}}}".format(origin.summary(), width_value, height_value)
class CGPoint(object):
"""
CGPoint wrapper
:param lldb.SBValue element: CGPoint object
"""
def __init__(self, element):
super(CGPoint, self).__init__()
self.element = element
def summary(self):
"""
:return: CGPoint summary
:rtype: str
"""
x = self.element.GetChildMemberWithName("x")
y = self.element.GetChildMemberWithName("y")
x_value = float(x.GetValue())
y_value = float(y.GetValue())
return "{{{}, {}}}".format(x_value, y_value)
def normalize_summary(summary):
"""
Normalize summary by removing "'" and "@" characters
:param str summary: Summary string to normalize
:return: Normalized summary string
:rtype: str
"""
return summary.lstrip("@").strip('"')
def normalize_array_description(description):
"""
Normalize array object description by removing "<" and ">" characters and content between them.
:param str description: Array object description
:return: Normalized array object description string
:rtype: str
"""
return re.sub("^(<.*>)", "", description).strip()
_uikit_imported = False
def import_uikit():
"""
Import UIKit framework to the debugger
"""
global _uikit_imported
if _uikit_imported:
return
_uikit_imported = True
fb.evaluateExpressionValue("@import UIKit")
def debug(element):
"""
Debug helper
:param lldb.SBValue element: Element to debug
"""
print("---")
print("element: {}".format(element))
print("element class: {}".format(element.__class__))
print("element name: {}".format(element.GetName()))
print("element type name: {}".format(element.GetTypeName()))
print("element value: {}".format(element.GetValue()))
print("element value class: {}".format(element.GetValue().__class__))
print("element value type: {}".format(element.GetValueType()))
print("element value signed: {0}({0:#x})".format(element.GetValueAsSigned()))
print("element value unsigned: {0}({0:#x})".format(element.GetValueAsUnsigned()))
print("element summary: {}".format(element.GetSummary()))
print("element description: {}".format(element.GetObjectDescription()))
print("element children num: {}".format(element.GetNumChildren()))
for i in range(0, element.GetNumChildren()):
child = element.GetChildAtIndex(i)
""":type: lldb.SBValue"""
print("element child {:02}: {}".format(i, child.GetName()))
print("===")