Module: Appium::Android

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

Defined Under Namespace

Classes: AndroidElements

Constant Summary collapse

TextView =
'android.widget.TextView'.freeze
Button =
'android.widget.Button'.freeze
ImageButton =
'android.widget.ImageButton'.freeze
EditText =
'android.widget.EditText'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(_mod) ⇒ Object



10
11
12
# File 'lib/appium_lib/android/mobile_methods.rb', line 10

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator'
end

.uiautomator_findObject

find_element/s can be used with a [UISelector](developer.android.com/tools/help/uiautomator/UiSelector.html).

“‘ruby

find_elements :uiautomator, 'new UiSelector().clickable(true)'

“‘



10
11
12
# File 'lib/appium_lib/android/mobile_methods.rb', line 10

def extended(_mod)
  ::Appium::Driver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator'
end

Instance Method Details

#_client_xpath(opts = {}) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/appium_lib/android/client_xpath.rb', line 20

def _client_xpath(opts = {})
  root_node = Nokogiri::XML(get_source).children.first

  instance = Hash.new(-1)

  root_node.traverse do |node|
    number = instance[node.name] += 1
    node.set_attribute 'instance', number
  end

  nodes = root_node.xpath(opts[:xpath])
  first = opts[:first]

  _nodeset_to_uiselector nodes: nodes, first: first
end

#_fix_android_native_source(source) ⇒ Object



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

def _fix_android_native_source(source)
  # <android.app.ActionBar$Tab
  # <android.app.ActionBar  $  Tab

  # find each closing tag that contains a dollar sign.
  source.scan(/<\/([^>]*\$[^>]*)>/).flatten.uniq.each do |problem_tag|
    # "android.app.ActionBar$Tab"
    before, after = problem_tag.split('$')
    before.strip!
    after.strip!

    fixed = "#{before}.#{after}"

    # now escape . in before/after because they're used in regex
    before.gsub!('.', '\.')
    after.gsub!('.', '\.')

    #  <android.app.ActionBar$Tab   => <android.app.ActionBar.Tab
    # </android.app.ActionBar$Tab> => </android.app.ActionBar.Tab>
    source = source.gsub(/<#{before}\s*\$\s*#{after}/,
                         "<#{fixed}").gsub(/<\/#{before}\s*\$\s*#{after}>/, "</#{fixed}>")
  end

  source
end

#_nodeset_to_uiselector(opts = {}) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/appium_lib/android/client_xpath.rb', line 5

def _nodeset_to_uiselector(opts = {})
  results = ''

  nodes = opts[:nodes]
  first = opts[:first]

  nodes = [nodes[0]] if first

  nodes.each do |node|
    results += %(new UiSelector().className("#{node.name}").instance(#{node.attr('instance')});)
  end

  results.strip
end

#_parse_current_app_line(line) ⇒ Object

noinspection RubyArgCount



172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/appium_lib/android/helper.rb', line 172

def _parse_current_app_line(line)
  match = line.match(/ ([^\/ ]+\/[^ }]+)[ }]/)
  return nil unless match && match[1]

  pair = match[1].split '/'
  pkg  = pair.first
  act  = pair.last
  OpenStruct.new(line:     line,
                 package:  pkg,
                 activity: act,
                 am_start: pkg + '/' + act)
end

#alert_acceptvoid

This method returns an undefined value.

Accept the alert. The last button is considered “accept.”



13
14
15
# File 'lib/appium_lib/android/element/alert.rb', line 13

def alert_accept
  last_button.click
end

#alert_accept_textString

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



20
21
22
# File 'lib/appium_lib/android/element/alert.rb', line 20

def alert_accept_text
  last_button.text
end

#alert_click(value) ⇒ void

This method returns an undefined value.

Click the first alert button that contains value or by index.



6
7
8
# File 'lib/appium_lib/android/element/alert.rb', line 6

def alert_click(value)
  button(value).click
end

#alert_dismissvoid

This method returns an undefined value.

Dismiss the alert. The first button is considered “dismiss.”



27
28
29
# File 'lib/appium_lib/android/element/alert.rb', line 27

def alert_dismiss
  first_button.click
end

#alert_dismiss_textString

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



34
35
36
# File 'lib/appium_lib/android/element/alert.rb', line 34

def alert_dismiss_text
  first_button.text
end

#button(value) ⇒ Button

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



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/appium_lib/android/element/button.rb', line 43

def button(value)
  # Don't use ele_index because that only works on one element type.
  # Android needs to combine button and image button to match iOS.
  if value.is_a? Numeric
    index = value
    raise "#{index} is not a valid index. Must be >= 1" if index <= 0

    return find_element :uiautomator, _button_visible_selectors(index: index)
  end

  find_element :uiautomator, _button_contains_string(value)
end

#button_exact(value) ⇒ Button

Find the first button that exactly matches value.



89
90
91
# File 'lib/appium_lib/android/element/button.rb', line 89

def button_exact(value)
  find_element :uiautomator, _button_exact_string(value)
end

#buttons(value = false) ⇒ Array<Button>

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



60
61
62
63
# File 'lib/appium_lib/android/element/button.rb', line 60

def buttons(value = false)
  return find_elements :uiautomator, _button_visible_selectors unless value
  find_elements :uiautomator, _button_contains_string(value)
end

#buttons_exact(value) ⇒ Array<Button>

Find all buttons that exactly match value.



96
97
98
# File 'lib/appium_lib/android/element/button.rb', line 96

def buttons_exact(value)
  find_elements :uiautomator, _button_exact_string(value)
end

#client_xpath(xpath) ⇒ Object



36
37
38
# File 'lib/appium_lib/android/client_xpath.rb', line 36

def client_xpath(xpath)
  find_element :uiautomator, _client_xpath(xpath: xpath, first: true)
end

#client_xpaths(xpath) ⇒ Object



40
41
42
# File 'lib/appium_lib/android/client_xpath.rb', line 40

def client_xpaths(xpath)
  find_elements :uiautomator, _client_xpath(xpath: xpath, first: false)
end

#complex_find_contains(element, value) ⇒ Element

Find the first element that contains value



308
309
310
# File 'lib/appium_lib/android/helper.rb', line 308

def complex_find_contains(element, value)
  find_element :uiautomator, string_visible_contains(element, value)
end

#complex_find_exact(class_name, value) ⇒ Element

Find the first element exactly matching value



345
346
347
# File 'lib/appium_lib/android/helper.rb', line 345

def complex_find_exact(class_name, value)
  find_element :uiautomator, string_visible_exact(class_name, value)
end

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

Find all elements containing value



316
317
318
# File 'lib/appium_lib/android/helper.rb', line 316

def complex_finds_contains(element, value)
  find_elements :uiautomator, string_visible_contains(element, value)
end

#complex_finds_exact(class_name, value) ⇒ Element

Find all elements exactly matching value



353
354
355
# File 'lib/appium_lib/android/helper.rb', line 353

def complex_finds_exact(class_name, value)
  find_elements :uiautomator, string_visible_exact(class_name, value)
end

#current_appObject

example line: “mFocusedApp=AppWindowTokentoken=Token{b128add0

ActivityRecord{b1264d10 u0 com.example.android.apis/.ApiDemos t23}}"


164
165
166
167
168
# File 'lib/appium_lib/android/helper.rb', line 164

def current_app
  line = `adb shell dumpsys window windows`.each_line.grep(/mFocusedApp/).first.strip

  _parse_current_app_line line
end

#ele_index(class_name, index) ⇒ Element

Find the element of type class_name at matching index.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/appium_lib/android/helper.rb', line 205

def ele_index(class_name, index)
  results = tags(class_name)
  if index == 'last()'
    index = results.length
    index -= 1 if index >= 0
  else
    raise 'Index must be >= 1' unless index >= 1
    index -= 1 if index >= 1
  end

  # uiautomator has issues with index/instance so calculate the index
  # client side.
  results[index]
end

#find(value) ⇒ Element

Find the first element containing value



6
7
8
# File 'lib/appium_lib/android/element/generic.rb', line 6

def find(value)
  complex_find_contains '*', value
end

#find_exact(value) ⇒ Element

Find the first element exactly matching value



20
21
22
# File 'lib/appium_lib/android/element/generic.rb', line 20

def find_exact(value)
  complex_find_exact '*', value
end

#finds(value) ⇒ Array<Element>

Find all elements containing value



13
14
15
# File 'lib/appium_lib/android/element/generic.rb', line 13

def finds(value)
  complex_finds_contains '*', value
end

#finds_exact(value) ⇒ Array<Element>

Find all elements exactly matching value



27
28
29
# File 'lib/appium_lib/android/element/generic.rb', line 27

def finds_exact(value)
  complex_finds_exact '*', value
end

#first_buttonButton

Find the first button.



67
68
69
# File 'lib/appium_lib/android/element/button.rb', line 67

def first_button
  find_element :uiautomator, _button_visible_selectors(button_index: 0, image_button_index: 0)
end

#first_ele(class_name) ⇒ Element

Find the first element that matches class_name



223
224
225
# File 'lib/appium_lib/android/helper.rb', line 223

def first_ele(class_name)
  tag(class_name)
end

#first_textTextView

Find the first TextView.



26
27
28
# File 'lib/appium_lib/android/element/text.rb', line 26

def first_text
  first_ele TextView
end

#first_textfieldEditText

Find the first EditText.



25
26
27
# File 'lib/appium_lib/android/element/textfield.rb', line 25

def first_textfield
  first_ele EditText
end

#get_android_inspect(class_name = false) ⇒ String

Android only. Returns a string containing interesting elements. The text, content description, and id are returned. if false (default) then all classes will be inspected



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/appium_lib/android/helper.rb', line 126

def get_android_inspect(class_name = false)
  source = get_source

  doctype_string = '<!doctyp'
  source_header  = source[0..doctype_string.length].downcase
  source_is_html = source_header.start_with?(doctype_string, '<html')

  parser = if source_is_html # parse html from webview
             @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Common::HTMLElements.new)
           else
             @android_native_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new)
           end
  parser.document.reset # ensure document is reset before parsing
  parser.document.filter = class_name
  parser.parse source
  result = parser.document.result
  parser.document.reset # clean up any created objects after parsing
  result
end

#get_sourceString

Returns XML string for the current page Fixes uiautomator’s $ in node names. ‘android.app.ActionBar$Tab` becomes `android.app.ActionBar.Tab`



361
362
363
364
365
# File 'lib/appium_lib/android/helper.rb', line 361

def get_source
  src = @driver.page_source
  src = _fix_android_native_source src unless src && src.start_with?('<html>')
  src
end

#id(id) ⇒ Element

Find the first matching element by id



188
189
190
191
# File 'lib/appium_lib/android/helper.rb', line 188

def id(id)
  # Android auto resolves strings.xml ids
  find_element :id, id
end

#ids(id) ⇒ Element

Find all matching elements by id



196
197
198
199
# File 'lib/appium_lib/android/helper.rb', line 196

def ids(id)
  # Android auto resolves strings.xml ids
  find_elements :id, id
end

#last_buttonButton

Find the last button.



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/appium_lib/android/element/button.rb', line 73

def last_button
  # uiautomator index doesn't support last
  # and it's 0 indexed
  button_index = tags(Button).length
  button_index -= 1 if button_index > 0
  image_button_index = tags(ImageButton).length
  image_button_index -= 1 if image_button_index > 0

  find_element :uiautomator,
               _button_visible_selectors(button_index: button_index,
                                         image_button_index: image_button_index)
end

#last_ele(class_name) ⇒ Element

Find the last element that matches class_name



230
231
232
# File 'lib/appium_lib/android/helper.rb', line 230

def last_ele(class_name)
  ele_index class_name, 'last()'
end

#last_textTextView

Find the last TextView.



32
33
34
# File 'lib/appium_lib/android/element/text.rb', line 32

def last_text
  last_ele TextView
end

#last_textfieldEditText

Find the last EditText.



31
32
33
# File 'lib/appium_lib/android/element/textfield.rb', line 31

def last_textfield
  last_ele EditText
end

#page(opts = {}) ⇒ void

This method returns an undefined value.

Intended for use with console. Inspects and prints the current page. Will return XHTML for Web contexts because of a quirk with Nokogiri. if nil (default) then all classes will be inspected



152
153
154
155
156
# File 'lib/appium_lib/android/helper.rb', line 152

def page(opts = {})
  class_name = opts.is_a?(Hash) ? opts.fetch(:class, nil) : opts
  puts get_android_inspect class_name
  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
# File 'lib/appium_lib/android/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)
      send_keys text
    end
  end
end

#resource_id(string, on_match) ⇒ String

Detects if the string represents a resourceId resourceId is only supported on API >= 18 devices



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/appium_lib/android/helper.rb', line 259

def resource_id(string, on_match)
  return '' unless string

  # unquote the string
  # "com.example.Test:id/enter" -> com.example.Test:id/enter
  unquote = string.match(/"(.+)"/)
  string = unquote[1] if unquote

  # java_package : type / name
  #
  # com.example.Test:id/enter
  #
  # ^[a-zA-Z_]      - Java package must start with letter or underscore
  # [a-zA-Z0-9\._]* - Java package may contain letters, numbers, periods and underscores
  # :               - : ends the package and starts the type
  # [^\/]+          - type is made up of at least one non-/ characters
  # \\/             - / ends the type and starts the name
  # [\S]+$          - the name contains at least one non-space character and then the line is ended
  resource_id = /^[a-zA-Z_][a-zA-Z0-9\._]*:[^\/]+\/[\S]+$/
  string.match(resource_id) ? on_match : ''
end

#scroll_to(text, scrollable_index = 0) ⇒ Element

Scroll to the first element containing target text or description.



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

def scroll_to(text, scrollable_index = 0)
  text = %("#{text}")

  args = scroll_uiselector("new UiSelector().textContains(#{text})", scrollable_index) +
         scroll_uiselector("new UiSelector().descriptionContains(#{text})", scrollable_index) +
         scroll_uiselector(resource_id(text, "new UiSelector().resourceId(#{text});"), scrollable_index)

  find_element :uiautomator, args
end

#scroll_to_exact(text, scrollable_index = 0) ⇒ Element

Scroll to the first element with the exact target text or description.



54
55
56
57
58
59
60
61
62
# File 'lib/appium_lib/android/element/generic.rb', line 54

def scroll_to_exact(text, scrollable_index = 0)
  text = %("#{text}")

  args = scroll_uiselector("new UiSelector().text(#{text})", scrollable_index) +
         scroll_uiselector("new UiSelector().description(#{text})", scrollable_index) +
         scroll_uiselector(resource_id(text, "new UiSelector().resourceId(#{text});"), scrollable_index)

  find_element :uiautomator, args
end

#scroll_uiselector(content, index = 0) ⇒ Object



32
33
34
# File 'lib/appium_lib/android/element/generic.rb', line 32

def scroll_uiselector(content, index = 0)
  "new UiScrollable(new UiSelector().scrollable(true).instance(#{index})).scrollIntoView(#{content}.instance(0));"
end

#sourcevoid

This method returns an undefined value.

Prints xml of the current page



116
117
118
# File 'lib/appium_lib/android/helper.rb', line 116

def source
  _print_source get_source
end

#string_visible_contains(class_name, value) ⇒ String

Returns a string that matches the first element that contains value

example: complex_find_contains ‘UIATextField’, ‘sign in’



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/appium_lib/android/helper.rb', line 288

def string_visible_contains(class_name, value)
  value = %("#{value}")

  if class_name == '*'
    return (resource_id(value, "new UiSelector().resourceId(#{value});") +
      "new UiSelector().descriptionContains(#{value});" \
      "new UiSelector().textContains(#{value});")
  end

  class_name = %("#{class_name}")

  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
    "new UiSelector().className(#{class_name}).descriptionContains(#{value});" \
    "new UiSelector().className(#{class_name}).textContains(#{value});"
end

#string_visible_exact(class_name, value) ⇒ String

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



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/appium_lib/android/helper.rb', line 325

def string_visible_exact(class_name, value)
  value = %("#{value}")

  if class_name == '*'
    return (resource_id(value, "new UiSelector().resourceId(#{value});") +
      "new UiSelector().description(#{value});" \
      "new UiSelector().text(#{value});")
  end

  class_name = %("#{class_name}")

  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
    "new UiSelector().className(#{class_name}).description(#{value});" \
    "new UiSelector().className(#{class_name}).text(#{value});"
end

#tag(class_name) ⇒ Element

Find the first element of type class_name



238
239
240
# File 'lib/appium_lib/android/helper.rb', line 238

def tag(class_name)
  find_element :class, class_name
end

#tags(class_name) ⇒ Element

Find all elements of type class_name



246
247
248
# File 'lib/appium_lib/android/helper.rb', line 246

def tags(class_name)
  find_elements :class, class_name
end

#text(value) ⇒ TextView

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



10
11
12
13
# File 'lib/appium_lib/android/element/text.rb', line 10

def text(value)
  return ele_index TextView, value if value.is_a? Numeric
  complex_find_contains TextView, value
end

#text_exact(value) ⇒ TextView

Find the first TextView that exactly matches value.



39
40
41
# File 'lib/appium_lib/android/element/text.rb', line 39

def text_exact(value)
  complex_find_exact TextView, value
end

#textfield(value) ⇒ EditText

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



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

def textfield(value)
  return ele_index EditText, value if value.is_a? Numeric
  complex_find_contains EditText, value
end

#textfield_exact(value) ⇒ EditText

Find the first EditText that exactly matches value.



38
39
40
# File 'lib/appium_lib/android/element/textfield.rb', line 38

def textfield_exact(value)
  complex_find_exact EditText, value
end

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

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



18
19
20
21
# File 'lib/appium_lib/android/element/textfield.rb', line 18

def textfields(value = false)
  return tags EditText unless value
  complex_finds_contains EditText, value
end

#textfields_exact(value) ⇒ Array<EditText>

Find all EditTexts that exactly match value.



45
46
47
# File 'lib/appium_lib/android/element/textfield.rb', line 45

def textfields_exact(value)
  complex_finds_exact EditText, value
end

#texts(value = false) ⇒ Array<TextView>

Find all TextViews containing value. If value is omitted, all texts are returned.



19
20
21
22
# File 'lib/appium_lib/android/element/text.rb', line 19

def texts(value = false)
  return tags TextView unless value
  complex_finds_contains TextView, value
end

#texts_exact(value) ⇒ Array<TextView>

Find all TextViews that exactly match value.



46
47
48
# File 'lib/appium_lib/android/element/text.rb', line 46

def texts_exact(value)
  complex_finds_exact TextView, value
end