Module: Appium::Ios

Defined in:
lib/appium_lib/ios/patch.rb,
lib/appium_lib/ios/errors.rb,
lib/appium_lib/ios/helper.rb,
lib/appium_lib/ios/element/text.rb,
lib/appium_lib/ios/element/alert.rb,
lib/appium_lib/ios/element/button.rb,
lib/appium_lib/ios/mobile_methods.rb,
lib/appium_lib/ios/element/generic.rb,
lib/appium_lib/ios/element/textfield.rb

Defined Under Namespace

Classes: CommandError, UITestElementsPrinter

Constant Summary collapse

UIAStaticText =
'UIAStaticText'.freeze
XCUIElementTypeStaticText =
'XCUIElementTypeStaticText'.freeze
UIAButton =
'UIAButton'.freeze
XCUIElementTypeButton =
'XCUIElementTypeButton'.freeze
UIATextField =
'UIATextField'.freeze
UIASecureTextField =
'UIASecureTextField'.freeze
XCUIElementTypeTextField =
'XCUIElementTypeTextField'.freeze
XCUIElementTypeSecureTextField =
'XCUIElementTypeSecureTextField'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(_mod) ⇒ Object



32
33
34
35
36
# File 'lib/appium_lib/ios/mobile_methods.rb', line 32

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation'
  ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string'
  ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
end

.ios_class_chain_findObject

Only for XCUITest(WebDriverAgent) find_element/s can be used with a [class chain]( github.com/facebook/WebDriverAgent/wiki/Queries)

“‘ruby

# select the third child button of the first child window element
find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]'
# select all the children windows
find_elements :class_chain, 'XCUIElementTypeWindow'
# select the second last child of the second child window
find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]'

“‘



32
33
34
35
36
# File 'lib/appium_lib/ios/mobile_methods.rb', line 32

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation'
  ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string'
  ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
end

.ios_predicate_string_findObject

find_element/s can be used with a [Predicates](developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html)

“‘ruby

find_elements :predicate, "isWDVisible == 1"
find_elements :predicate, 'wdName == "Buttons"'
find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1'

“‘



32
33
34
35
36
# File 'lib/appium_lib/ios/mobile_methods.rb', line 32

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation'
  ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string'
  ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
end

.uiautomation_findObject

find_element/s can be used with a [UIAutomation command](developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930).

“‘ruby

find_elements :uiautomation, 'elements()

“‘



32
33
34
35
36
# File 'lib/appium_lib/ios/mobile_methods.rb', line 32

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation'
  ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string'
  ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
end

Instance Method Details

#_all_pred(opts) ⇒ Object

predicate - the predicate to evaluate on the main app

visible - if true, only visible elements are returned. default true



591
592
593
594
595
596
# File 'lib/appium_lib/ios/helper.rb', line 591

def _all_pred(opts)
  predicate = opts[:predicate]
  raise 'predicate must be provided' unless predicate
  visible = opts.fetch :visible, true
  %($.mainApp().getAllWithPredicate("#{predicate}", #{visible});)
end

#_by_json(opts) ⇒ Object

For Appium(automation name), not XCUITest typeArray - array of string types to search for. Example: [“UIAStaticText”] onlyFirst - boolean. returns only the first result if true. Example: true onlyVisible - boolean. returns only visible elements if true. Example: true target - string. the target value to search for. Example: “Buttons, Various uses of UIButton” substring - boolean. matches on substrings if true otherwise an exact mathc is required. Example: true insensitive - boolean. ignores case sensitivity if true otherwise it’s case sensitive. Example: true

opts = {

typeArray: ["UIAStaticText"],
onlyFirst: true,
onlyVisible: true,
name: {
  target: "Buttons, Various uses of UIButton",
  substring: false,
  insensitive: false,
},
label: {
  target: "Buttons, Various uses of UIButton",
  substring: false,
  insensitive: false,
},
value: {
  target: "Buttons, Various uses of UIButton",
  substring: false,
  insensitive: false,
}

}



674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/appium_lib/ios/helper.rb', line 674

def _by_json(opts)
  valid_keys   = [:typeArray, :onlyFirst, :onlyVisible, :name, :label, :value]
  unknown_keys = opts.keys - valid_keys
  raise "Unknown keys: #{unknown_keys}" unless unknown_keys.empty?

  type_array = opts[:typeArray]
  raise 'typeArray must be an array' unless type_array.is_a? Array

  only_first = opts[:onlyFirst]
  raise 'onlyFirst must be a boolean' unless [true, false].include? only_first

  only_visible = opts[:onlyVisible]
  raise 'onlyVisible must be a boolean' unless [true, false].include? only_visible

  # name/label/value are optional. when searching for class only, then none
  # will be present.
  _validate_object opts[:name], opts[:label], opts[:value]

  # note that mainWindow is sometimes nil so it's passed as a param
  # $._elementOrElementsByType will validate that the window isn't nil
  element_or_elements_by_type = <<-JS
    (function() {
      var opts = #{opts.to_json};
      var result = false;

      try {
        result = $._elementOrElementsByType($.mainWindow(), opts);
      } catch (e) {
      }

      return result;
    })();
  JS

  res = execute_script element_or_elements_by_type
  res ? res : raise(Appium::Ios::CommandError, 'mainWindow is nil')
end

#_validate_object(*objects) ⇒ Object



625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/appium_lib/ios/helper.rb', line 625

def _validate_object(*objects)
  raise 'objects must be an array' unless objects.is_a? Array
  objects.each do |obj|
    next unless obj # obj may be nil. if so, ignore.

    valid_keys   = [:target, :substring, :insensitive]
    unknown_keys = obj.keys - valid_keys
    raise "Unknown keys: #{unknown_keys}" unless unknown_keys.empty?

    target = obj[:target]
    raise 'target must be a string' unless target.is_a? String

    substring = obj[:substring]
    raise 'substring must be a boolean' unless [true, false].include? substring

    insensitive = obj[:insensitive]
    raise 'insensitive must be a boolean' unless [true, false].include? insensitive
  end
end

#alert_acceptvoid

This method returns an undefined value.

Accept the alert.



5
6
7
8
9
# File 'lib/appium_lib/ios/element/alert.rb', line 5

def alert_accept
  # @driver.switch_to.alert.accept
  # ".switch_to.alert" calls alert_text so use bridge directly
  driver.send(:bridge).accept_alert
end

#alert_dismissvoid

This method returns an undefined value.

Dismiss the alert.



13
14
15
16
17
# File 'lib/appium_lib/ios/element/alert.rb', line 13

def alert_dismiss
  # @driver.switch_to.alert.dismiss
  # ".switch_to.alert" calls alert_text so use bridge directly
  driver.send(:bridge).dismiss_alert
end

#button(value) ⇒ UIAButton|XCUIElementTypeButton

Find the first UIAButton|XCUIElementTypeButton that contains value or by index. If int then the UIAButton|XCUIElementTypeButton at that index is returned.

Parameters:

  • value (String, Integer)

    the value to exactly match.

Returns:



16
17
18
19
20
21
22
23
24
25
# File 'lib/appium_lib/ios/element/button.rb', line 16

def button(value)
  # return button at index.
  return ele_index button_class, value if value.is_a? Numeric

  if automation_name_is_xcuitest?
    raise_error_if_no_element buttons(value).first
  else
    ele_by_json_visible_contains button_class, value
  end
end

#button_classString

Returns Class name for button.

Returns:

  • (String)

    Class name for button



8
9
10
# File 'lib/appium_lib/ios/element/button.rb', line 8

def button_class
  automation_name_is_xcuitest? ? XCUIElementTypeButton : UIAButton
end

#button_exact(value) ⇒ UIAButton|XCUIElementTypeButton

Find the first UIAButton|XCUIElementTypeButton that exactly matches value.

Parameters:

  • value (String)

    the value to match exactly

Returns:



59
60
61
62
63
64
65
# File 'lib/appium_lib/ios/element/button.rb', line 59

def button_exact(value)
  if automation_name_is_xcuitest?
    raise_error_if_no_element buttons_exact(value).first
  else
    ele_by_json_visible_exact button_class, value
  end
end

#buttons(value = false) ⇒ Array<UIAButton|XCUIElementTypeButton>

Find all UIAButtons|XCUIElementTypeButtons containing value. If value is omitted, all UIAButtons|XCUIElementTypeButtons are returned.

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:



31
32
33
34
35
36
37
38
39
40
# File 'lib/appium_lib/ios/element/button.rb', line 31

def buttons(value = false)
  return tags button_class unless value

  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate_include(class_name: button_class, value: value)
    select_visible_elements elements
  else
    eles_by_json_visible_contains button_class, value
  end
end

#buttons_exact(value) ⇒ Array<UIAButton|XCUIElementTypeButton>

Find all UIAButtons|XCUIElementTypeButtons that exactly match value.

Parameters:

  • value (String)

    the value to match exactly

Returns:



70
71
72
73
74
75
76
77
# File 'lib/appium_lib/ios/element/button.rb', line 70

def buttons_exact(value)
  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate(class_name: button_class, value: value)
    select_visible_elements elements
  else
    eles_by_json_visible_exact button_class, value
  end
end

#ele_by_json(opts) ⇒ Object

see eles_by_json



730
731
732
733
734
735
# File 'lib/appium_lib/ios/helper.rb', line 730

def ele_by_json(opts)
  opts[:onlyFirst] = true
  result           = _by_json(opts).first
  raise _no_such_element if result.nil?
  result
end

#ele_by_json_visible_contains(element, value) ⇒ Element

Find the first element that contains value. For Appium(automation name), not XCUITest

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


478
479
480
# File 'lib/appium_lib/ios/helper.rb', line 478

def ele_by_json_visible_contains(element, value)
  ele_by_json string_visible_contains element, value
end

#ele_by_json_visible_exact(element, value) ⇒ Element

Find the first element exactly matching value For Appium(automation name), not XCUITest

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


517
518
519
# File 'lib/appium_lib/ios/helper.rb', line 517

def ele_by_json_visible_exact(element, value)
  ele_by_json string_visible_exact element, value
end

#ele_index(class_name, index) ⇒ Element

Get the element of type class_name at matching index.

Parameters:

  • class_name (String)

    the class name to find

  • index (Integer)

    the index

Returns:

  • (Element)


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/appium_lib/ios/helper.rb', line 212

def ele_index(class_name, index)
  raise 'Index must be >= 1' unless index == 'last()' || (index.is_a?(Integer) && index >= 1)
  elements = tags(class_name)

  if index == 'last()'
    result = elements.last
  else
    # elements array is 0 indexed
    index -= 1
    result = elements[index]
  end

  raise _no_such_element if result.nil?
  result
end

#ele_with_pred(opts) ⇒ Element

returns element matching predicate contained in the main app

predicate - the predicate to evaluate on the main app

visible - if true, only visible elements are returned. default true

Returns:

  • (Element)


604
605
606
607
# File 'lib/appium_lib/ios/helper.rb', line 604

def ele_with_pred(opts)
  # true = return only visible
  find_element(:uiautomation, _all_pred(opts))
end

#eles_by_json(opts) ⇒ Object

For Appium(automation name), not XCUITest example usage:

eles_by_json({

typeArray: ["UIAStaticText"],
onlyVisible: true,
name: {
  target: "Buttons, Various uses of UIButton",
  substring: false,
  insensitive: false,
},

})



724
725
726
727
# File 'lib/appium_lib/ios/helper.rb', line 724

def eles_by_json(opts)
  opts[:onlyFirst] = false
  _by_json opts
end

#eles_by_json_visible_contains(element, value) ⇒ Array<Element>

Find all elements containing value For Appium(automation name), not XCUITest

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


487
488
489
# File 'lib/appium_lib/ios/helper.rb', line 487

def eles_by_json_visible_contains(element, value)
  eles_by_json string_visible_contains element, value
end

#eles_by_json_visible_exact(element, value) ⇒ Element

Find all elements exactly matching value For Appium(automation name), not XCUITest

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


526
527
528
# File 'lib/appium_lib/ios/helper.rb', line 526

def eles_by_json_visible_exact(element, value)
  eles_by_json string_visible_exact element, value
end

#eles_with_pred(opts) ⇒ Array<Element>

returns elements matching predicate contained in the main app

predicate - the predicate to evaluate on the main app

visible - if true, only visible elements are returned. default true

Returns:

  • (Array<Element>)


615
616
617
# File 'lib/appium_lib/ios/helper.rb', line 615

def eles_with_pred(opts)
  find_elements(:uiautomation, _all_pred(opts))
end

#empty(ele) ⇒ Object



58
59
60
# File 'lib/appium_lib/ios/helper.rb', line 58

def empty(ele)
  (ele['name'] || ele['label'] || ele['value']).nil?
end

#find(value) ⇒ Element

Find the first element containing value

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Element)


6
7
8
9
10
11
12
# File 'lib/appium_lib/ios/element/generic.rb', line 6

def find(value)
  if automation_name_is_xcuitest?
    raise_error_if_no_element finds(value).first
  else
    ele_by_json_visible_contains '*', value
  end
end

#find_ele_by_attr(class_name, attr, value) ⇒ Element

Find the first element exactly matching class and attribute value. Note: Uses XPath Note: For XCUITest, this method return ALL elements include displayed or not displayed elements.

Parameters:

  • class_name (String)

    the class name to search for

  • attr (String)

    the attribute to inspect

  • value (String)

    the expected value of the attribute

Returns:

  • (Element)


248
249
250
# File 'lib/appium_lib/ios/helper.rb', line 248

def find_ele_by_attr(class_name, attr, value)
  @driver.find_element :xpath, string_attr_exact(class_name, attr, value)
end

#find_ele_by_attr_include(class_name, attr, value) ⇒ Element

Get the first tag by attribute that exactly matches value. Note: Uses XPath

Parameters:

  • class_name (String)

    the tag name to match

  • attr (String)

    the attribute to compare

  • value (String)

    the value of the attribute that the element must include

Returns:

  • (Element)

    the element of type tag who’s attribute includes value



307
308
309
# File 'lib/appium_lib/ios/helper.rb', line 307

def find_ele_by_attr_include(class_name, attr, value)
  @driver.find_element :xpath, string_attr_include(class_name, attr, value)
end

#find_ele_by_predicate(class_name: '*', value:) ⇒ Element

Find the first element exactly matching attribute case insensitive value. Note: Uses Predicate

Parameters:

  • value (String)

    the expected value of the attribute

Returns:

  • (Element)


280
281
282
283
284
# File 'lib/appium_lib/ios/helper.rb', line 280

def find_ele_by_predicate(class_name: '*', value:)
  elements = find_eles_by_predicate(class_name: class_name, value: value)
  raise _no_such_element if elements.empty?
  elements.first
end

#find_ele_by_predicate_include(class_name: '*', value:) ⇒ Element

Get the first elements that include insensitive value. Note: Uses Predicate

Parameters:

  • value (String)

    the value of the attribute that the element must include

Returns:

  • (Element)

    the element of type tag who’s attribute includes value



325
326
327
328
329
# File 'lib/appium_lib/ios/helper.rb', line 325

def find_ele_by_predicate_include(class_name: '*', value:)
  elements = find_eles_by_predicate_include(class_name: class_name, value: value)
  raise _no_such_element if elements.empty?
  elements.first
end

#find_eles_by_attr(class_name, attr, value) ⇒ Array<Element>

Find all elements exactly matching class and attribute value. Note: Uses XPath Note: For XCUITest, this method return ALL elements include displayed or not displayed elements.

Parameters:

  • class_name (String)

    the class name to match

  • attr (String)

    the attribute to compare

  • value (String)

    the value of the attribute that the element must have

Returns:

  • (Array<Element>)


259
260
261
# File 'lib/appium_lib/ios/helper.rb', line 259

def find_eles_by_attr(class_name, attr, value)
  @driver.find_elements :xpath, string_attr_exact(class_name, attr, value)
end

#find_eles_by_attr_include(class_name, attr, value) ⇒ Array<Element>

Get tags by attribute that include value. Note: Uses XPath

Parameters:

  • class_name (String)

    the tag name to match

  • attr (String)

    the attribute to compare

  • value (String)

    the value of the attribute that the element must include

Returns:

  • (Array<Element>)

    the elements of type tag who’s attribute includes value



317
318
319
# File 'lib/appium_lib/ios/helper.rb', line 317

def find_eles_by_attr_include(class_name, attr, value)
  @driver.find_elements :xpath, string_attr_include(class_name, attr, value)
end

#find_eles_by_predicate(class_name: '*', value:) ⇒ Array<Element>

Find all elements exactly matching attribute case insensitive value. Note: Uses Predicate

Parameters:

  • value (String)

    the value of the attribute that the element must have

  • class_name (String) (defaults to: '*')

    the tag name to match

Returns:

  • (Array<Element>)


291
292
293
294
295
296
297
298
299
# File 'lib/appium_lib/ios/helper.rb', line 291

def find_eles_by_predicate(class_name: '*', value:)
  predicate =  if class_name == '*'
                 %(name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}")
               else
                 %(type == "#{class_name}" && ) +
                   %((name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}"))
               end
  @driver.find_elements :predicate, predicate
end

#find_eles_by_predicate_include(class_name: '*', value:) ⇒ Array<Element>

Get elements that include case insensitive value. Note: Uses Predicate

Parameters:

  • value (String)

    the value of the attribute that the element must include

  • class_name (String) (defaults to: '*')

    the tag name to match

Returns:

  • (Array<Element>)

    the elements of type tag who’s attribute includes value



336
337
338
339
340
341
342
343
344
# File 'lib/appium_lib/ios/helper.rb', line 336

def find_eles_by_predicate_include(class_name: '*', value:)
  predicate = if class_name == '*'
                %(name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}")
              else
                %(type == "#{class_name}" && ) +
                  %((name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}"))
              end
  @driver.find_elements :predicate, predicate
end

#find_exact(value) ⇒ Element

Find the first element exactly matching value

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Element)


29
30
31
32
33
34
35
# File 'lib/appium_lib/ios/element/generic.rb', line 29

def find_exact(value)
  if automation_name_is_xcuitest?
    raise_error_if_no_element finds_exact(value).first
  else
    ele_by_json_visible_exact '*', value
  end
end

#finds(value) ⇒ Array<Element>

Find all elements containing value

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


17
18
19
20
21
22
23
24
# File 'lib/appium_lib/ios/element/generic.rb', line 17

def finds(value)
  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate_include value: value
    select_visible_elements elements
  else
    eles_by_json_visible_contains '*', value
  end
end

#finds_exact(value) ⇒ Array<Element>

Find all elements exactly matching value

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


40
41
42
43
44
45
46
47
# File 'lib/appium_lib/ios/element/generic.rb', line 40

def finds_exact(value)
  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate value: value
    select_visible_elements elements
  else
    eles_by_json_visible_exact '*', value
  end
end

#first_buttonUIAButton|XCUIElementTypeButton

Find the first UIAButton|XCUIElementTypeButton.



44
45
46
# File 'lib/appium_lib/ios/element/button.rb', line 44

def first_button
  first_ele button_class
end

#first_ele(class_name) ⇒ Element

Get the first tag that matches class_name

Parameters:

  • class_name (String)

    the tag to match

Returns:

  • (Element)


349
350
351
# File 'lib/appium_lib/ios/helper.rb', line 349

def first_ele(class_name)
  ele_index class_name, 1
end

#first_textUIAStaticText|XCUIElementTypeStaticText

Find the first UIAStaticText|XCUIElementTypeStaticText.



43
44
45
# File 'lib/appium_lib/ios/element/text.rb', line 43

def first_text
  first_ele static_text_class
end

#first_textfieldTextField

Find the first TextField.

Returns:

  • (TextField)


100
101
102
103
104
105
106
# File 'lib/appium_lib/ios/element/textfield.rb', line 100

def first_textfield
  if automation_name_is_xcuitest?
    _textfield_with_predicate
  else
    ele_by_json _textfield_visible
  end
end

#fix_space(s) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/appium_lib/ios/helper.rb', line 63

def fix_space(s)
  # if s is an int, we can't call .empty
  return nil if s.nil? || (s.respond_to?(:empty) && s.empty?)
  # ints don't respond to force encoding
  # ensure we're converting to a string
  unless s.respond_to? :force_encoding
    s_s = s.to_s
    return s_s.empty? ? nil : s_s
  end
  # char code 160 (name, label) vs 32 (value) will break comparison.
  # convert string to binary and remove 160.
  # \xC2\xA0
  s = s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
  s.empty? ? nil : s.force_encoding('UTF-8')
end

#get_page(element = source_window(0), class_name = nil) ⇒ String

Returns a string of interesting elements. iOS only.

Defaults to inspecting the 1st windows source only. use get_page(get_source) for all window sources

Parameters:

  • element (Hash) (defaults to: source_window(0))

    a customizable set of options

  • class_name (Hash) (defaults to: nil)

    a customizable set of options

Options Hash (element):

  • the (Object)

    element to search. omit to search everything

Options Hash (class_name):

  • the (String, Symbol)

    class name to filter on. case insensitive include match.

Returns:

  • (String)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/appium_lib/ios/helper.rb', line 53

def get_page(element = source_window(0), class_name = nil)
  lazy_load_strings # populate @strings_xml
  class_name = class_name.to_s.downcase

  # @private
  def empty(ele)
    (ele['name'] || ele['label'] || ele['value']).nil?
  end

  # @private
  def fix_space(s)
    # if s is an int, we can't call .empty
    return nil if s.nil? || (s.respond_to?(:empty) && s.empty?)
    # ints don't respond to force encoding
    # ensure we're converting to a string
    unless s.respond_to? :force_encoding
      s_s = s.to_s
      return s_s.empty? ? nil : s_s
    end
    # char code 160 (name, label) vs 32 (value) will break comparison.
    # convert string to binary and remove 160.
    # \xC2\xA0
    s = s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
    s.empty? ? nil : s.force_encoding('UTF-8')
  end

  unless empty(element) || element['visible'] == false
    name    = fix_space element['name']
    label   = fix_space element['label']
    value   = fix_space element['value']
    hint    = fix_space element['hint']
    visible = fix_space element['visible']
    type    = fix_space element['type']

    # if class_name is set, mark non-matches as invisible
    visible = (type.downcase.include? class_namet).to_s if class_name
    if visible && visible == 'true'

      _print_attr(type, name, label, value, hint)

      # there may be many ids with the same value.
      # output all exact matches.
      attributes = [name, label, value, hint].select { |attr| !attr.nil? }
      partial    = {}
      id_matches = @strings_xml.select do |key, val|
        next if val.nil? || val.empty?
        partial[key] = val if attributes.detect { |attr| attr.include?(val) }
        attributes.detect { |attr| val == attr }
      end

      # If there are no exact matches, display partial matches.
      id_matches = partial if id_matches.empty?

      unless id_matches.empty?
        match_str = ''
        max_len   = id_matches.keys.max_by(&:length).length

        # [0] = key, [1] = val
        id_matches.each do |key, val|
          arrow_space = ' ' * (max_len - key.length).to_i
          match_str += ' ' * 7 + "#{key} #{arrow_space}=> #{val}\n"
        end
        puts "   id: #{match_str.strip}\n"
      end
    end
  end

  children = element['children']
  children.each { |c| get_page c, class_name } if children
  nil
end

#get_sourceString

Returns XML string for the current page Same as driver.page_source

Returns:

  • (String)


740
741
742
# File 'lib/appium_lib/ios/helper.rb', line 740

def get_source
  @driver.page_source
end

#hide_ios_keyboard(close_key = 'Done') ⇒ void

This method returns an undefined value.

For Appium(automation name), not XCUITest If there’s no keyboard, then do nothing. If there’s no close key, fallback to window tap. If close key is present then tap it.

Parameters:

  • close_key (String) (defaults to: 'Done')

    close key to tap. Default value is ‘Done’



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/appium_lib/ios/helper.rb', line 537

def hide_ios_keyboard(close_key = 'Done')
  #
  # TODO: there are many various ways to hide the keyboard that work in different
  # app specific circumstances. webview keyboard will require a window.tap for example.
  #
  # Find the top left corner of the keyboard and move up 10 pixels (origin.y - 10)
  # now swipe down until the end of the window - 10 pixels.
  # -10 to ensure we're not going outside the window bounds.
  #
  # Swiping inside the keyboard will not dismiss it.
  #
  # If the 'Done' key exists then that should be pressed to dismiss the keyboard
  # because swiping to dismiss works only if such key doesn't exist.
  #
  # Don't use window.tap. See https://github.com/appium/appium-uiauto/issues/28
  #
  dismiss_keyboard = <<-JS.strip
  if (!au.mainApp().keyboard().isNil()) {
    var key = au.mainApp().keyboard().buttons()['#{close_key}']
    if (key.isNil()) {
      var startY = au.mainApp().keyboard().rect().origin.y - 10;
      var endY = au.mainWindow().rect().size.height - 10;
      au.flickApp(0, startY, 0, endY);
    } else {
      key.tap();
    }
    au.delay(1000);
  }
  JS

  ignore do
    # wait 5 seconds for a wild keyboard to appear. if the textfield is disabled
    # then setValue will work, however the keyboard will never display
    # because users are normally not allowed to type into it.
    wait_true(5) do
      execute_script '!au.mainApp().keyboard().isNil()'
    end

    # dismiss keyboard
    execute_script dismiss_keyboard
  end

  # wait 5 seconds for keyboard to go away.
  # if the keyboard isn't dismissed then wait_true will error.
  wait_true(5) do
    execute_script 'au.mainApp().keyboard().isNil()'
  end
end

#id(id) ⇒ Element

Find by id

Parameters:

  • id (String)

    the id to search for

Returns:

  • (Element)


193
194
195
# File 'lib/appium_lib/ios/helper.rb', line 193

def id(id)
  find_element(:id, id)
end

#ios_password(length = 1) ⇒ String

iOS only. On Android uiautomator always returns an empty string for EditText password.

Password character returned from value of UIASecureTextField

Parameters:

  • length (Integer) (defaults to: 1)

    the length of the password to generate

Returns:

  • (String)

    the returned string is of size length



41
42
43
# File 'lib/appium_lib/ios/helper.rb', line 41

def ios_password(length = 1)
  8226.chr('UTF-8') * length
end

#ios_versionArray<Integer>

Return the iOS version as an array of integers

Returns:

  • (Array<Integer>)


199
200
201
202
203
204
205
206
# File 'lib/appium_lib/ios/helper.rb', line 199

def ios_version
  if automation_name_is_xcuitest?
    @driver.capabilities['platformVersion']
  else
    ios_version = execute_script 'UIATarget.localTarget().systemVersion()'
    ios_version.split('.').map(&:to_i)
  end
end

#last_buttonUIAButton|XCUIElementTypeButton

TODO: add documentation regarding previous element.

Previous UIAElement is differ from UIAButton|XCUIElementTypeButton. So, the results are different.

Find the last UIAButton|XCUIElementTypeButton.



52
53
54
# File 'lib/appium_lib/ios/element/button.rb', line 52

def last_button
  last_ele button_class
end

#last_ele(class_name) ⇒ Element

Get the last tag that matches class_name

Parameters:

  • class_name (String)

    the tag to match

Returns:

  • (Element)


356
357
358
359
360
361
362
363
364
# File 'lib/appium_lib/ios/helper.rb', line 356

def last_ele(class_name)
  if automation_name_is_xcuitest?
    visible_elements = tags class_name
    raise _no_such_element if visible_elements.empty?
    visible_elements.last
  else
    ele_index class_name, 'last()'
  end
end

#last_textUIAStaticText|XCUIElementTypeStaticText

Find the last UIAStaticText|XCUIElementTypeStaticText.



49
50
51
# File 'lib/appium_lib/ios/element/text.rb', line 49

def last_text
  last_ele static_text_class
end

#last_textfieldTextField

Find the last TextField.

Returns:

  • (TextField)


110
111
112
113
114
115
116
117
118
# File 'lib/appium_lib/ios/element/textfield.rb', line 110

def last_textfield
  result = if automation_name_is_xcuitest?
             _textfields_with_predicate.last
           else
             eles_by_json(_textfield_visible).last
           end
  raise _no_such_element if result.nil?
  result
end

#page(opts = {}) ⇒ void

This method returns an undefined value.

Prints a string of interesting elements to the console.

Example

“‘ruby page class: :UIAButton # filter on buttons page window: 1 # show source for window 1 page class: :UIAButton, window: 1 “`

Parameters:

  • window (Hash)

    a customizable set of options

  • class (Hash)

    a customizable set of options



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/appium_lib/ios/helper.rb', line 139

def page(opts = {})
  if opts.is_a?(Hash)
    window_number = opts.fetch :window, -1
    class_name    = opts.fetch :class, nil
  else
    window_number = -1
    class_name    = opts
  end
  # current_context may be nil which breaks start_with
  if current_context && current_context.start_with?('WEBVIEW')
    s      = get_source
    parser = @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Common::HTMLElements.new)
    parser.document.reset
    parser.document.filter = class_name
    parser.parse s
    parser.document.result
  else

    s = source_window(window_number || 0)
    parser = Nokogiri::XML::SAX::Parser.new(UITestElementsPrinter.new)
    if class_name
      parser.document.filter = class_name.is_a?(Symbol) ? class_name.to_s : class_name
    end
    parser.parse s
    nil
  end
end

#page_window(window_number = 0) ⇒ void

This method returns an undefined value.

Prints parsed page source to console.

example: page_window 0

Parameters:

  • window_number (Integer) (defaults to: 0)

    the int index of the target window



185
186
187
188
# File 'lib/appium_lib/ios/helper.rb', line 185

def page_window(window_number = 0)
  get_page source_window window_number
  nil
end

#patch_webdriver_elementObject

class_eval inside a method because class Selenium::WebDriver::Element will trigger as soon as the file is required. in contrast a method will trigger only when invoked.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/appium_lib/ios/patch.rb', line 7

def patch_webdriver_element
  Selenium::WebDriver::Element.class_eval do
    # Enable access to iOS accessibility label
    # accessibility identifier is supported as 'name'
    def label
      attribute('label')
    end

    # Cross platform way of entering text into a textfield
    def type(text)
      if $driver.automation_name_is_xcuitest?
        send_keys text
      else
        $driver.execute_script %(au.getElement('#{ref}').setValue('#{text}');)
      end
    end # def type
  end # Selenium::WebDriver::Element.class_eval
end

#secure_text_field_classString

Returns Class name for secure text field.

Returns:

  • (String)

    Class name for secure text field



15
16
17
# File 'lib/appium_lib/ios/element/textfield.rb', line 15

def secure_text_field_class
  automation_name_is_xcuitest? ? XCUIElementTypeSecureTextField : UIASecureTextField
end

#sourcevoid

This method returns an undefined value.

Prints xml of the current page



621
622
623
# File 'lib/appium_lib/ios/helper.rb', line 621

def source
  _print_source get_source
end

#source_window(_window_number = 0) ⇒ JSON

Gets the JSON source of window number

Parameters:

  • _window_number (Integer) (defaults to: 0)

    The int index of the target window

Returns:

  • (JSON)


170
171
172
173
174
175
176
177
# File 'lib/appium_lib/ios/helper.rb', line 170

def source_window(_window_number = 0)
  # TODO: update comments
  # appium 1.0 still returns JSON when getTree() is invoked so this
  # doesn't need to change to XML. If getTree() is removed then
  # source_window will need to parse the elements of getTreeForXML()\
  # https://github.com/appium/appium-uiauto/blob/247eb71383fa1a087ff8f8fc96fac25025731f3f/uiauto/appium/element.js#L145
  get_source
end

#static_text_classString

Returns Class name for text.

Returns:

  • (String)

    Class name for text



8
9
10
# File 'lib/appium_lib/ios/element/text.rb', line 8

def static_text_class
  automation_name_is_xcuitest? ? XCUIElementTypeStaticText : UIAStaticText
end

#string_attr_exact(class_name, attr, value) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/appium_lib/ios/helper.rb', line 229

def string_attr_exact(class_name, attr, value)
  if automation_name_is_xcuitest?
    if attr == '*'
      %((//#{class_name})[@*[.="#{value}"]])
    else
      %((//#{class_name})[@#{attr}="#{value}"])
    end
  else
    %(//#{class_name}[@visible="true" and @#{attr}="#{value}"])
  end
end

#string_attr_include(class_name, attr, value) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
# File 'lib/appium_lib/ios/helper.rb', line 264

def string_attr_include(class_name, attr, value)
  if automation_name_is_xcuitest?
    if attr == '*'
      %((//#{class_name})[@*[contains(translate(., "#{value.upcase}", "#{value}"), "#{value}")]])
    else
      %((//#{class_name})[contains(translate(@#{attr}, "#{value.upcase}", "#{value}"), "#{value}")])
    end
  else
    %(//#{class_name}[@visible="true" and contains(translate(@#{attr},"#{value.upcase}", "#{value}"), "#{value}")])
  end
end

#string_visible_contains(element, value) ⇒ String

Returns an object that matches the first element that contains value

example: ele_by_json_visible_contains ‘UIATextField’, ‘sign in’

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (String)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/appium_lib/ios/helper.rb', line 457

def string_visible_contains(element, value)
  contains = {
    target:      value,
    substring:   true,
    insensitive: true
  }

  {
    typeArray:   [element],
    onlyVisible: true,
    name:        contains,
    label:       contains,
    value:       contains
  }
end

#string_visible_exact(element, value) ⇒ String

Create an object to exactly match the first element with target value

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (String)


496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/appium_lib/ios/helper.rb', line 496

def string_visible_exact(element, value)
  exact = {
    target:      value,
    substring:   false,
    insensitive: false
  }

  {
    typeArray:   [element],
    onlyVisible: true,
    name:        exact,
    label:       exact,
    value:       exact
  }
end

#tag(class_name) ⇒ Element

Returns the first visible element matching class_name

Parameters:

  • class_name (String)

    the class_name to search for

Returns:

  • (Element)


370
371
372
373
374
375
376
# File 'lib/appium_lib/ios/helper.rb', line 370

def tag(class_name)
  if automation_name_is_xcuitest?
    raise_error_if_no_element tags(class_name).first
  else
    ele_by_json(typeArray: [class_name], onlyVisible: true)
  end
end

#tags(class_name) ⇒ Element

Returns all visible elements matching class_name

Parameters:

  • class_name (String)

    the class_name to search for

Returns:

  • (Element)


382
383
384
385
386
387
388
389
# File 'lib/appium_lib/ios/helper.rb', line 382

def tags(class_name)
  if automation_name_is_xcuitest?
    elements = @driver.find_elements :class, class_name
    select_visible_elements elements
  else
    eles_by_json(typeArray: [class_name], onlyVisible: true)
  end
end

#tags_exact(class_names:, value: nil) ⇒ Array[Element]

Returns all visible elements matching class_names and value. This method calls find_element/s and element.value/text many times. So, if you set many class_names, this method’s performance become worse.

Parameters:

  • class_names (Array[String])

    the class_names to search for

  • value (String) (defaults to: nil)

    the value to search for

Returns:

  • (Array[Element])


427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/appium_lib/ios/helper.rb', line 427

def tags_exact(class_names:, value: nil)
  return unless class_names.is_a? Array

  if automation_name_is_xcuitest?
    c_names = class_names.map { |class_name| %(type == "#{class_name}") }.join(' || ')

    predicate = if value
                  %((#{c_names}) && ) +
                    %((name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}"))
                else
                  c_names
                end

    elements = @driver.find_elements :predicate, predicate
    select_visible_elements elements
  else
    class_names.flat_map do |class_name|
      value ? eles_by_json_visible_exact(class_name, value) : tags(class_name)
    end
  end
end

#tags_include(class_names:, value: nil) ⇒ Array[Element]

Returns all visible elements matching class_names and value This method calls find_element/s and element.value/text many times. So, if you set many class_names, this method’s performance become worse.

Parameters:

  • class_names (Array[String])

    the class_names to search for

  • value (String) (defaults to: nil)

    the value to search for

Returns:

  • (Array[Element])


398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/appium_lib/ios/helper.rb', line 398

def tags_include(class_names:, value: nil)
  return unless class_names.is_a? Array

  if automation_name_is_xcuitest?
    c_names = class_names.map { |class_name| %(type == "#{class_name}") }.join(' || ')

    predicate = if value
                  %((#{c_names}) && ) +
                    %((name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}"))
                else
                  c_names
                end

    elements = @driver.find_elements :predicate, predicate
    select_visible_elements elements
  else
    class_names.flat_map do |class_name|
      value ? eles_by_json_visible_contains(class_name, value) : tags(class_name)
    end
  end
end

#text(value) ⇒ UIAStaticText|XCUIElementTypeStaticText

Find the first UIAStaticText|XCUIElementTypeStaticText that contains value or by index. If int then the UIAStaticText|XCUIElementTypeStaticText at that index is returned.

Parameters:

  • value (String, Integer)

    the value to find.

Returns:



16
17
18
19
20
21
22
23
24
# File 'lib/appium_lib/ios/element/text.rb', line 16

def text(value)
  return ele_index static_text_class, value if value.is_a? Numeric

  if automation_name_is_xcuitest?
    raise_error_if_no_element texts(value).first
  else
    ele_by_json_visible_contains static_text_class, value
  end
end

#text_exact(value) ⇒ UIAStaticText|XCUIElementTypeStaticText

Find the first UIAStaticText|XCUIElementTypeStaticText that exactly matches value.

Parameters:

  • value (String)

    the value to match exactly

Returns:



56
57
58
59
60
61
62
# File 'lib/appium_lib/ios/element/text.rb', line 56

def text_exact(value)
  if automation_name_is_xcuitest?
    raise_error_if_no_element texts_exact(value).first
  else
    ele_by_json_visible_exact static_text_class, value
  end
end

#text_field_classString

Returns Class name for text field.

Returns:

  • (String)

    Class name for text field



10
11
12
# File 'lib/appium_lib/ios/element/textfield.rb', line 10

def text_field_class
  automation_name_is_xcuitest? ? XCUIElementTypeTextField : UIATextField
end

#textfield(value) ⇒ TextField

Find the first TextField that contains value or by index. Note: Uses XPath If int then the TextField at that index is returned.

Parameters:

  • value (String, Integer)

    the text to match exactly.

Returns:

  • (TextField)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/appium_lib/ios/element/textfield.rb', line 60

def textfield(value)
  if value.is_a? Numeric
    index = value
    raise "#{index} is not a valid index. Must be >= 1" if index <= 0
    index -= 1 # eles_by_json and _textfields_with_predicate is 0 indexed.
    result = if automation_name_is_xcuitest?
               _textfields_with_predicate[index]
             else
               eles_by_json(_textfield_visible)[index]
             end
    raise _no_such_element if result.nil?
    return result

  end

  if automation_name_is_xcuitest?
    raise_error_if_no_element textfields(value).first
  else
    ele_by_json _textfield_contains_string value
  end
end

#textfield_exact(value) ⇒ TextField

Find the first TextField that exactly matches value.

Parameters:

  • value (String)

    the value to match exactly

Returns:

  • (TextField)


123
124
125
126
127
128
129
# File 'lib/appium_lib/ios/element/textfield.rb', line 123

def textfield_exact(value)
  if automation_name_is_xcuitest?
    raise_error_if_no_element textfields_exact(value).first
  else
    ele_by_json _textfield_exact_string value
  end
end

#textfields(value = false) ⇒ Array<TextField>

Find all TextFields containing value. If value is omitted, all TextFields are returned.

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:

  • (Array<TextField>)


86
87
88
89
90
91
92
93
94
95
96
# File 'lib/appium_lib/ios/element/textfield.rb', line 86

def textfields(value = false)
  if automation_name_is_xcuitest?
    return tags_include(class_names: [text_field_class, secure_text_field_class]) unless value

    elements = tags_include class_names: [text_field_class, secure_text_field_class], value: value
    select_visible_elements elements
  else
    return eles_by_json _textfield_visible unless value
    eles_by_json _textfield_contains_string value
  end
end

#textfields_exact(value) ⇒ Array<TextField>

Find all TextFields that exactly match value.

Parameters:

  • value (String)

    the value to match exactly

Returns:

  • (Array<TextField>)


134
135
136
137
138
139
140
141
# File 'lib/appium_lib/ios/element/textfield.rb', line 134

def textfields_exact(value)
  if automation_name_is_xcuitest?
    elements = tags_exact class_names: [text_field_class, secure_text_field_class], value: value
    select_visible_elements elements
  else
    eles_by_json _textfield_exact_string value
  end
end

#texts(value = false) ⇒ Array<UIAStaticText|XCUIElementTypeStaticText>

Find all UIAStaticTexts|XCUIElementTypeStaticTexts containing value. If value is omitted, all UIAStaticTexts|XCUIElementTypeStaticTexts are returned

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:



30
31
32
33
34
35
36
37
38
39
# File 'lib/appium_lib/ios/element/text.rb', line 30

def texts(value = false)
  return tags static_text_class unless value

  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate_include(class_name: static_text_class, value: value)
    select_visible_elements elements
  else
    eles_by_json_visible_contains static_text_class, value
  end
end

#texts_exact(value) ⇒ Array<UIAStaticText|XCUIElementTypeStaticText>

Find all UIAStaticTexts|XCUIElementTypeStaticTexts that exactly match value.

Parameters:

  • value (String)

    the value to match exactly

Returns:



67
68
69
70
71
72
73
74
# File 'lib/appium_lib/ios/element/text.rb', line 67

def texts_exact(value)
  if automation_name_is_xcuitest?
    elements = find_eles_by_predicate(class_name: static_text_class, value: value)
    select_visible_elements elements
  else
    eles_by_json_visible_exact static_text_class, value
  end
end