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("===")