Module: Appium::Ios

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

Instance Method Summary collapse

Instance Method Details

#alert_acceptvoid

This method returns an undefined value.

Accept the alert.



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

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

#alert_accept_textString

Get the text of the alert’s accept button. The last button is considered “accept.”

Returns:

  • (String)


39
40
41
42
43
44
# File 'lib/appium_lib/ios/element/alert.rb', line 39

def alert_accept_text
  a = @driver.find_element(:tag_name, :alert)
  return if a.nil?
  b = a.find_elements(:tag_name, :button)
  b.last.text if b && b.size >= 1
end

#alert_click(value) ⇒ void

This method returns an undefined value.

iOS only Tap the alert button identified by value.

Click the ok button:

alert_click 'OK'

Click the first button:

alert_click 0

Parameters:

  • value (Integer, String)

    either an integer index of the button or the button’s name



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

def alert_click value
  value = "'#{value}'" if value.is_a?(String)
  @driver.execute_script "UIATarget.localTarget().frontMostApp().alert().buttons()[#{value}].tap();"
end

#alert_dismissvoid

This method returns an undefined value.

Dismiss the alert.



48
49
50
51
52
# File 'lib/appium_lib/ios/element/alert.rb', line 48

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

#alert_dismiss_textString

Get the text of the alert’s dismiss button. The first button is considered “dismiss.”

Returns:

  • (String)


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

def alert_dismiss_text
  a = @driver.find_element(:tag_name, :alert)
  return if a.nil?
  b = a.find_elements(:tag_name, :button)
  b.first.text if b && b.size >= 1
end

#alert_textString

Get the alert message text.

Returns:

  • (String)


21
22
23
24
25
26
# File 'lib/appium_lib/ios/element/alert.rb', line 21

def alert_text
  # this will call get text twice so call bridge directly
  # ".switch_to.alert" calls it once, then ".text" another time.
  # @driver.switch_to.alert.text
  driver.send(:bridge).getAlertText
end

#all_ele_js(predicate) ⇒ String

Returns the completed JavaScript program.

Parameters:

  • predicate (String)

    the predicate

Returns:

  • (String)

    the completed JavaScript program



52
53
54
55
56
# File 'lib/appium_lib/ios/element/generic.rb', line 52

def all_ele_js predicate
  (<<-JS).strip # remove trailing newline
    au.mainApp.getAllWithPredicate("#{predicate}");
  JS
end

#e_textfieldsArray<Textfield>

Get an array of textfield elements.

Returns:

  • (Array<Textfield>)


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

def e_textfields
  execute_script textfield_js
end

#empty(ele) ⇒ Object



93
94
95
# File 'lib/appium_lib/ios/helper.rb', line 93

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

#fast_durationFloat

The fastest duration that can be used on iOS.

Returns:

  • (Float)


157
158
159
# File 'lib/appium_lib/ios/helper.rb', line 157

def fast_duration
  0.5
end

#find(text) ⇒ Element

Return the first element matching text.

Parameters:

  • text (String)

    the text to search for

Returns:

  • (Element)

    the first matching element

Raises:

  • (Selenium::WebDriver::Error::NoSuchElementError)


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

def find text
  ele = nil
  # prefer value search. this may error with:
  # Can't use in/contains operator with collection 1
  js = first_ele_js "value contains[c] '#{text}'"
  ele = ignore { execute_script js }

  # now search name and label if the value search didn't match.
  unless ele
    js = first_ele_js "name contains[c] '#{text}' || label contains[c] '#{text}'"
    ele = ignore { execute_script js }
  end

  # manually raise error if no element was found
  raise Selenium::WebDriver::Error::NoSuchElementError, 'An element could not be located on the page using the given search parameters.' unless ele

  ele
end

#find_2_eles_attr(tag_name_1, tag_name_2, attribute) ⇒ Array<String>

iOS only. Android doesn’t use find_2_eles_attr. Get an array of attribute values from elements exactly matching tag name.

Parameters:

  • tag_name_1 (String)

    the 1st tag name to find

  • tag_name_2 (String)

    the 2nd tag name to find

  • attribute (String)

    the attribute to collect

Returns:

  • (Array<String>)

    an array of strings containing the attribute from found elements of type tag_name.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/appium_lib/ios/helper.rb', line 29

def find_2_eles_attr tag_name_1, tag_name_2, attribute
  # Use au.lookup(tag_name) instead of $(tag_name)
  # See https://github.com/appium/appium/issues/214
  js = %Q(
      var eles = au.lookup('#{tag_name_1}');
      eles = $(eles.concat(au.lookup('#{tag_name_2}')));
      var result = [];
      for (var a = 0, length = eles.length; a < length; a++) {
        result.push(eles[a].#{attribute}());
      }
      result
    )

  @driver.execute_script js
end

#find_eles_attr(tag_name, attribute) ⇒ Array<String>

iOS only. Android uses uiautomator instead of uiautomation. Get an array of attribute values from elements exactly matching tag name.

Parameters:

  • tag_name (String)

    the tag name to find

  • attribute (String)

    the attribute to collect

Returns:

  • (Array<String>)

    an array of strings containing the attribute from found elements of type tag_name.



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

def find_eles_attr tag_name, attribute
  # Use au.lookup(tag_name) instead of $(tag_name)
  # See https://github.com/appium/appium/issues/214
  js = %Q(
      var eles = au.lookup('#{tag_name}');
      var result = [];
      for (var a = 0, length = eles.length; a < length; a++) {
        result.push(eles[a].#{attribute}());
      }
      result
    )

  @driver.execute_script js
end

#finds(text) ⇒ Array<Element>

Return all elements matching text.

Parameters:

  • text (String)

    the text to search for

Returns:

  • (Array<Element>)

    all matching elements



83
84
85
86
87
88
89
90
91
92
# File 'lib/appium_lib/ios/element/generic.rb', line 83

def finds text
  eles = []
  # value contains may error
  js = all_ele_js "value contains[c] '#{text}'"
  eles = ignore { execute_script js }

  js = all_ele_js "name contains[c] '#{text}' || label contains[c] '#{text}'"
  eles += ignore { execute_script js }
  eles
end

#first_ele_js(predicate) ⇒ String

returnElems requires a wrapped $(element). set to empty array when length is zero to prevent hang.

UIAElementNil when not matched

  1. secureTextFields

  2. textFields

  3. buttons

  4. elements

search takes the window to search. start searching webview first. window 0 is the main window. window 1 is the 1st webview (if it exists) must break instead of return because it’s not a function.

single element length is undefined when found and 0 when not found.

Parameters:

  • predicate (String)

    the predicate

Returns:

  • (String)

    the completed JavaScript program



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

def first_ele_js predicate
  (<<-JS).strip # remove trailing newline
     au.mainApp.getFirstWithPredicateWeighted("#{predicate}");
  JS
end

#first_textfieldTextfield

Get the first textfield element.

Returns:

  • (Textfield)


22
23
24
25
# File 'lib/appium_lib/ios/element/textfield.rb', line 22

def first_textfield
  js = textfield_js 'r = r.length > 0 ? $(r[0]) : r;'
  execute_script(js).first
end

#fix_space(s) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/appium_lib/ios/helper.rb', line 98

def fix_space s
  # ints don't respond to force encoding
  return s unless s.respond_to? :force_encoding
  # char code 160 (name, label) vs 32 (value) will break comparison.
  # convert string to binary and remove 160.
  # \xC2\xA0
  s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
end

#get_page(element = get_source) ⇒ String

Returns a string of interesting elements. iOS only.

Parameters:

  • element (Object) (defaults to: get_source)

    the element to search. omit to search everything

Returns:

  • (String)


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
124
125
126
127
128
129
130
131
# File 'lib/appium_lib/ios/helper.rb', line 90

def get_page element=get_source

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

  # @private
  def fix_space s
    # ints don't respond to force encoding
    return s unless s.respond_to? :force_encoding
    # char code 160 (name, label) vs 32 (value) will break comparison.
    # convert string to binary and remove 160.
    # \xC2\xA0
    s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
  end

  unless empty(element)
    puts "#{element['type']}"
    name = fix_space element['name']
    label = fix_space element['label']
    value = fix_space element['value']

    if name == label && name == value
      puts "   name, label, value: #{name}" if name
    elsif name == label
      puts "   name, label: #{name}" if name
      puts "   value: #{value}" if value
    elsif name == value
      puts "   name, value: #{name}" if name
      puts "  label: #{label}" if label
    else
      puts "   name: #{name}" if name
      puts "  label: #{label}" if label
      puts "  value: #{value}" if value
    end
  end

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

#get_page_classObject

Returns a string of class counts.



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
# File 'lib/appium_lib/ios/helper.rb', line 55

def get_page_class
  r = []
  run_internal = lambda do |node|
    if node.kind_of? Array
      node.each { |node| run_internal.call node }
      return
    end

    keys = node.keys
    return if keys.empty?
    r.push node['type'] if keys.include?('type')

    run_internal.call node['children'] if keys.include?('children')
  end
  json = get_source
  run_internal.call json['children']

  res = []
  r = r.sort
  r.uniq.each do |ele|
    res.push "#{r.count(ele)}x #{ele}\n"
  end
  count_sort  = ->(one,two) { two.match(/(\d+)x/)[1].to_i <=> one.match(/(\d+)x/)[1].to_i }
  res.sort(&count_sort).join ''
end

#last_textfieldTextfield

Get the last textfield element.

Returns:

  • (Textfield)


29
30
31
32
# File 'lib/appium_lib/ios/element/textfield.rb', line 29

def last_textfield
  js = textfield_js 'r = r.length > 0 ? $(r[r.length - 1]) : r;'
  execute_script(js).first
end

#name(name) ⇒ Element

Return the first element matching name. on Android name is content description on iOS name is the accessibility label or the text.

Parameters:

  • name (String)

    the name to search for

Returns:

  • (Element)

    the first matching element



117
118
119
# File 'lib/appium_lib/ios/element/generic.rb', line 117

def name name
  mobile :findElementNameContains, name: name
end

#names(name) ⇒ Array<Element>

Return all elements matching name. on Android name is content description on iOS name is the accessibility label or the text.

Parameters:

  • name (String)

    the name to search for

Returns:

  • (Array<Element>)

    all matching elements



126
127
128
129
130
131
# File 'lib/appium_lib/ios/element/generic.rb', line 126

def names name
  # :name is not consistent across iOS and Android so use custom JavaScript
  # https://github.com/appium/appium/issues/379
  js = all_ele_js "name contains[c] '#{name}' || label contains[c] '#{name}'"
  execute_script js
end

#pagevoid

This method returns an undefined value.

Prints a string of interesting elements to the console.



135
136
137
138
# File 'lib/appium_lib/ios/helper.rb', line 135

def page
  get_page
  nil
end

#page_classObject



81
82
83
84
# File 'lib/appium_lib/ios/helper.rb', line 81

def page_class
  puts get_page_class
  nil
end

#page_window(window_number = 0) ⇒ Object

Prints parsed page source to console. example: page_window 0

Parameters:

  • window_number (Integer) (defaults to: 0)

    the int index of the target window



150
151
152
153
# File 'lib/appium_lib/ios/helper.rb', line 150

def page_window window_number=0
  get_page source_window window_number
  nil
end

#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



50
51
52
# File 'lib/appium_lib/ios/helper.rb', line 50

def password length=1
  '' * length
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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/appium_lib/ios/patch.rb', line 7

def patch_webdriver_element
  Selenium::WebDriver::Element.class_eval do
    # Cross platform way of entering text into a textfield
    def type text
      # enter text then tap window to hide the keyboard.
=begin
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.
=end
      # type
      $driver.execute_script %(au.getElement('#{self.ref}').setValue('#{text}');)

      # wait for keyboard
      $driver.wait_true { $driver.execute_script %(au.mainApp.keyboard().type
() !== 'UIAElementNil') }

      # dismiss keyboard
      js = <<-JS
        if (au.mainApp.keyboard().type() !== "UIAElementNil") {
          var startY = au.mainApp.keyboard().rect().origin.y - 10;
          var endY = au.mainWindow.rect().size.height - 10;
          au.flickApp(0, startY, 0, endY);
        }
      JS

      $driver.execute_script js
    end
  end
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)


143
144
145
# File 'lib/appium_lib/ios/helper.rb', line 143

def source_window window_number=0
  execute_script "UIATarget.localTarget().frontMostApp().windows()[#{window_number}].getTree()"
end

#text(text) ⇒ Element

Return the first element matching text.

Parameters:

  • text (String)

    the text to search for

Returns:

  • (Element)

    the first matching element



97
98
99
100
# File 'lib/appium_lib/ios/element/generic.rb', line 97

def text text
  js = first_ele_js "value contains[c] '#{text}'"
  execute_script js
end

#textfield(text) ⇒ Textfield

Get the first textfield that matches text.

Parameters:

  • text (String, Integer)

    the text to match exactly. If int then the textfield at that index is returned.

Returns:

  • (Textfield)


37
38
39
40
41
42
43
44
45
46
# File 'lib/appium_lib/ios/element/textfield.rb', line 37

def textfield text
  # Don't use ele_index because that only works on one element type.
  # iOS needs to combine textfield and secure to match Android.
  if text.is_a? Numeric
    js = textfield_js "r = r.length > 0 ? $(r[#{text}]) : r;"
    return execute_script(js).first
  end

  textfield_include text
end

#textfield_exact(text) ⇒ Textfield

Get the first textfield that exactly matches text.

Parameters:

  • text (String)

    the text the textfield must exactly match

Returns:

  • (Textfield)


64
65
66
67
68
69
70
71
72
73
# File 'lib/appium_lib/ios/element/textfield.rb', line 64

def textfield_exact text
  # find_ele_by_text :textfield, text
  js = %Q(
    var t = au.getElementsByXpath('textfield[@text="#{text}"]').value;
    var s = au.getElementsByXpath('secure[@text="#{text}"]').value;
    t.concat(s)[0];
  )

  execute_script js
end

#textfield_include(text) ⇒ Textfield

Get the first textfield that includes text.

Parameters:

  • text (String)

    the text the textfield must include

Returns:

  • (Textfield)


51
52
53
54
55
56
57
58
59
# File 'lib/appium_lib/ios/element/textfield.rb', line 51

def textfield_include text
  js = %Q(
    var t = au.getElementsByXpath('textfield[contains(@text, "#{text}")]').value;
    var s = au.getElementsByXpath('secure[contains(@text, "#{text}")]').value;
    t.concat(s)[0];
  )

  execute_script js
end

#textfield_js(filter = '') ⇒ Object

Return combined lookup of textfield and secure with an optional filter. $() wrap is required for .each



78
79
80
81
82
83
84
85
86
# File 'lib/appium_lib/ios/element/textfield.rb', line 78

def textfield_js filter=''
%Q(
  var t = au.lookup('textfield');
  var s = au.lookup('secure');
  var r = $(t.concat(s));
  #{filter}
  au._returnElems(r);
)
end

#textfieldsArray<String>

Get an array of textfield texts.

Returns:

  • (Array<String>)


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

def textfields
  find_2_eles_attr :textfield, :secure, :text
end

#texts(text) ⇒ Array<Element>

Return all elements matching text.

Parameters:

  • text (String)

    the text to search for

Returns:

  • (Array<Element>)

    all matching elements



105
106
107
108
109
110
# File 'lib/appium_lib/ios/element/generic.rb', line 105

def texts text
  # XPath //* is not implemented on iOS
  # https://github.com/appium/appium/issues/430
  js = all_ele_js "value contains[c] '#{text}'"
  execute_script js
end