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'
Button =
'android.widget.Button'
ImageButton =
'android.widget.ImageButton'
EditText =
'android.widget.EditText'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(_mod) ⇒ Object



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

def extended(_mod)
  Selenium::WebDriver::SearchContext.class_eval do
    Selenium::WebDriver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator'
  end
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
13
14
# File 'lib/appium_lib/android/mobile_methods.rb', line 10

def extended(_mod)
  Selenium::WebDriver::SearchContext.class_eval do
    Selenium::WebDriver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator'
  end
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

#_resource_id(string, on_match) ⇒ String

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

Parameters:

  • string (String)

    the string check for a resourceId value will be auto unquoted

  • on_match (String)

    the string to return on resourceId match

Returns:

  • (String)

    empty string on failure, on_match on successful match



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

#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.”

Returns:

  • (String)


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.

Parameters:

  • value (Integer, String)

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



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.”

Returns:

  • (String)


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.

Parameters:

  • value (String, Integer)

    the value to exactly match.

Returns:



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
    fail "#{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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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.

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:



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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


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

Parameters:

  • class_name (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


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

Parameters:

  • element (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


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

Parameters:

  • class_name (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (Element)


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.

Parameters:

  • class_name (String)

    the class name to find

  • index (Integer)

    the index

Returns:

  • (Element)

    the found element of type class_name



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
    fail '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

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Element)


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

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Element)


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

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


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

Parameters:

  • value (String)

    the value to search for

Returns:

  • (Array<Element>)


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.

Returns:



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

Parameters:

  • class_name (String)

    the tag to match

Returns:

  • (Element)


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.

Returns:



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.

Returns:



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

Parameters:

  • class_name (String) (defaults to: false)

    the class name to filter on.

Returns:

  • (String)


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')

  if source_is_html # parse html from webview
    parser = @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Common::HTMLElements.new)
  else
    parser = @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

Returns:

  • (String)


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

Parameters:

  • id (String)

    the id to search for

Returns:

  • (Element)


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

Parameters:

  • id (String)

    the id to search for

Returns:

  • (Element)


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.

Returns:



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

Parameters:

  • class_name (String)

    the tag to match

Returns:

  • (Element)


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.

Returns:



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.

Returns:



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

Parameters:

  • class (Hash)

    a customizable set of options



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

#scroll_to(text) ⇒ Element

Scroll to the first element containing target text or description.

Parameters:

  • text (String)

    the text to search for in the text value and content description

Returns:

  • (Element)

    the element scrolled to



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

def scroll_to(text)
  text = %("#{text}")

  args = scroll_uiselector("new UiSelector().textContains(#{text})") +
         scroll_uiselector("new UiSelector().descriptionContains(#{text})")

  find_element :uiautomator, args
end

#scroll_to_exact(text) ⇒ Element

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

Parameters:

  • text (String)

    the text to search for in the text value and content description

Returns:

  • (Element)

    the element scrolled to



51
52
53
54
55
56
57
58
# File 'lib/appium_lib/android/element/generic.rb', line 51

def scroll_to_exact(text)
  text = %("#{text}")

  args = scroll_uiselector("new UiSelector().text(#{text})") +
         scroll_uiselector("new UiSelector().description(#{text})")

  find_element :uiautomator, args
end

#scroll_uiselector(content) ⇒ Object



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

def scroll_uiselector(content)
  "new UiScrollable(new UiSelector().scrollable(true).instance(0)).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’

Parameters:

  • class_name (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (String)


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

Parameters:

  • class_name (String)

    the class name for the element

  • value (String)

    the value to search for

Returns:

  • (String)


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

Parameters:

  • class_name (String)

    the class_name to search for

Returns:

  • (Element)


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

Parameters:

  • class_name (String)

    the class_name to search for

Returns:

  • (Element)


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.

Parameters:

  • value (String, Integer)

    the value to find.

Returns:



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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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.

Parameters:

  • value (String, Integer)

    the text to match exactly.

Returns:



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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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.

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:



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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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.

Parameters:

  • value (String) (defaults to: false)

    the value to search for

Returns:



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.

Parameters:

  • value (String)

    the value to match exactly

Returns:



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

def texts_exact(value)
  complex_finds_exact TextView, value
end