appium_lib 6.0.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +28 -0
  4. data/.travis.yml +10 -0
  5. data/Rakefile +9 -1
  6. data/android_tests/Gemfile +1 -1
  7. data/android_tests/Rakefile +20 -13
  8. data/android_tests/lib/android/specs/android/element/alert.rb +1 -1
  9. data/android_tests/lib/android/specs/android/element/button.rb +2 -2
  10. data/android_tests/lib/android/specs/android/element/generic.rb +1 -2
  11. data/android_tests/lib/android/specs/android/element/text.rb +2 -3
  12. data/android_tests/lib/android/specs/android/element/textfield.rb +2 -2
  13. data/android_tests/lib/android/specs/android/helper.rb +5 -3
  14. data/android_tests/lib/android/specs/android/patch.rb +2 -2
  15. data/android_tests/lib/android/specs/common/device.rb +16 -9
  16. data/android_tests/lib/android/specs/common/device_touchaction.rb +5 -2
  17. data/android_tests/lib/android/specs/common/element/window.rb +1 -1
  18. data/android_tests/lib/android/specs/common/helper.rb +14 -15
  19. data/android_tests/lib/android/specs/common/patch.rb +11 -9
  20. data/android_tests/lib/android/specs/common/version.rb +3 -3
  21. data/android_tests/lib/android/specs/common/web_context.rb +2 -3
  22. data/android_tests/lib/android/specs/driver.rb +38 -29
  23. data/android_tests/lib/android/specs/install.rb +3 -3
  24. data/android_tests/lib/format.rb +6 -8
  25. data/android_tests/lib/run.rb +25 -17
  26. data/android_tests/readme.md +4 -2
  27. data/appium_lib.gemspec +13 -11
  28. data/contributing.md +1 -1
  29. data/docs/android_docs.md +358 -274
  30. data/docs/ios_docs.md +333 -270
  31. data/docs/migration.md +10 -0
  32. data/docs_gen/make_docs.rb +3 -1
  33. data/ios_tests/Gemfile +1 -1
  34. data/ios_tests/Rakefile +17 -10
  35. data/ios_tests/appium.txt +1 -1
  36. data/ios_tests/lib/common.rb +8 -4
  37. data/ios_tests/lib/format.rb +5 -7
  38. data/ios_tests/lib/ios/specs/common/element/window.rb +1 -1
  39. data/ios_tests/lib/ios/specs/common/helper.rb +40 -39
  40. data/ios_tests/lib/ios/specs/common/patch.rb +15 -11
  41. data/ios_tests/lib/ios/specs/common/version.rb +3 -3
  42. data/ios_tests/lib/ios/specs/common/web_context.rb +1 -2
  43. data/ios_tests/lib/ios/specs/device/device.rb +7 -7
  44. data/ios_tests/lib/ios/specs/device/multi_touch.rb +6 -8
  45. data/ios_tests/lib/ios/specs/device/touch_actions.rb +12 -12
  46. data/ios_tests/lib/ios/specs/driver.rb +23 -22
  47. data/ios_tests/lib/ios/specs/ios/element/alert.rb +6 -2
  48. data/ios_tests/lib/ios/specs/ios/element/button.rb +2 -2
  49. data/ios_tests/lib/ios/specs/ios/element/generic.rb +1 -1
  50. data/ios_tests/lib/ios/specs/ios/element/text.rb +4 -1
  51. data/ios_tests/lib/ios/specs/ios/element/textfield.rb +6 -6
  52. data/ios_tests/lib/ios/specs/ios/helper.rb +5 -5
  53. data/ios_tests/lib/ios/specs/ios/patch.rb +2 -2
  54. data/ios_tests/lib/run.rb +1 -1
  55. data/ios_tests/readme.md +3 -3
  56. data/ios_tests/upload/sauce_storage.rb +8 -8
  57. data/ios_tests/upload/upload.rb +1 -1
  58. data/lib/appium_lib/android/client_xpath.rb +7 -7
  59. data/lib/appium_lib/android/element/alert.rb +2 -2
  60. data/lib/appium_lib/android/element/button.rb +16 -16
  61. data/lib/appium_lib/android/element/generic.rb +12 -13
  62. data/lib/appium_lib/android/element/text.rb +5 -5
  63. data/lib/appium_lib/android/element/textfield.rb +5 -5
  64. data/lib/appium_lib/android/helper.rb +82 -52
  65. data/lib/appium_lib/android/mobile_methods.rb +2 -2
  66. data/lib/appium_lib/android/patch.rb +3 -3
  67. data/lib/appium_lib/common/element/window.rb +1 -1
  68. data/lib/appium_lib/common/helper.rb +30 -35
  69. data/lib/appium_lib/common/patch.rb +22 -20
  70. data/lib/appium_lib/common/version.rb +3 -3
  71. data/lib/appium_lib/common/wait.rb +9 -10
  72. data/lib/appium_lib/device/device.rb +39 -33
  73. data/lib/appium_lib/device/multi_touch.rb +5 -7
  74. data/lib/appium_lib/device/touch_actions.rb +14 -15
  75. data/lib/appium_lib/driver.rb +97 -76
  76. data/lib/appium_lib/ios/element/alert.rb +1 -1
  77. data/lib/appium_lib/ios/element/button.rb +5 -5
  78. data/lib/appium_lib/ios/element/generic.rb +5 -6
  79. data/lib/appium_lib/ios/element/text.rb +5 -5
  80. data/lib/appium_lib/ios/element/textfield.rb +15 -15
  81. data/lib/appium_lib/ios/helper.rb +103 -90
  82. data/lib/appium_lib/ios/mobile_methods.rb +2 -2
  83. data/lib/appium_lib/ios/patch.rb +4 -4
  84. data/lib/appium_lib/logger.rb +7 -5
  85. data/lib/appium_lib/rails/duplicable.rb +3 -1
  86. data/readme.md +7 -1
  87. data/release_notes.md +152 -0
  88. metadata +28 -54
@@ -8,7 +8,7 @@ describe 'driver' do
8
8
  before_first
9
9
  end
10
10
 
11
- def is_sauce
11
+ def sauce?
12
12
  ENV['UPLOAD_FILE'] && ENV['SAUCE_USERNAME']
13
13
  end
14
14
 
@@ -20,7 +20,7 @@ describe 'driver' do
20
20
  t 'load_appium_txt' do
21
21
  # skip this test if we're using Sauce
22
22
  # the storage API doesn't have an on disk file
23
- skip if is_sauce
23
+ skip if sauce?
24
24
  appium_txt = File.expand_path(File.join(Dir.pwd, 'lib'))
25
25
  opts = Appium.load_appium_txt file: appium_txt, verbose: true
26
26
 
@@ -36,7 +36,7 @@ describe 'driver' do
36
36
  actual = driver_attributes
37
37
  actual[:caps][:app] = File.basename actual[:caps][:app]
38
38
  expected = { caps: { platformName: 'ios',
39
- platformVersion: '8.1',
39
+ platformVersion: '8.3',
40
40
  deviceName: 'iPhone Simulator',
41
41
  app: 'UICatalog.app' },
42
42
  custom_url: false,
@@ -82,7 +82,7 @@ describe 'driver' do
82
82
  t 'app_path attr' do
83
83
  apk_name = File.basename driver_attributes[:caps][:app]
84
84
 
85
- if is_sauce
85
+ if sauce?
86
86
  apk_name.must_equal 'sauce-storage:UICatalog6.1.app.zip'
87
87
  else
88
88
  apk_name.must_equal 'UICatalog.app'
@@ -92,7 +92,7 @@ describe 'driver' do
92
92
  # Only used for Sauce Labs
93
93
  t 'app_name attr' do
94
94
  name_attr = driver_attributes[:caps][:name]
95
- if is_sauce
95
+ if sauce?
96
96
  name_attr.must_equal 'appium_lib_ios'
97
97
  else
98
98
  name_attr.must_be_nil
@@ -101,7 +101,7 @@ describe 'driver' do
101
101
 
102
102
  t 'sauce_username attr' do
103
103
  sauce_username = driver_attributes[:sauce_username]
104
- if is_sauce
104
+ if sauce?
105
105
  sauce_username.must_equal 'appiumci'
106
106
  else
107
107
  sauce_username.must_be_nil
@@ -110,8 +110,8 @@ describe 'driver' do
110
110
 
111
111
  t 'sauce_access_key attr' do
112
112
  sauce_access_key = driver_attributes[:sauce_access_key]
113
- if is_sauce
114
- sauce_access_key.must_match /\h{8}-\h{4}-\h{4}-\h{4}-\h{12}/
113
+ if sauce?
114
+ sauce_access_key.must_match(/\h{8}-\h{4}-\h{4}-\h{4}-\h{12}/)
115
115
  else
116
116
  sauce_access_key.must_be_nil
117
117
  end
@@ -119,11 +119,6 @@ describe 'driver' do
119
119
  end
120
120
 
121
121
  describe 'Appium::Driver' do
122
- t '@@loaded' do
123
- loaded = $driver.class.class_variable_get :@@loaded
124
- loaded.must_equal true
125
- end
126
-
127
122
  t '$driver.class' do
128
123
  $driver.class.must_equal Appium::Driver
129
124
  end
@@ -136,10 +131,10 @@ describe 'driver' do
136
131
 
137
132
  t 'server_version' do
138
133
  server_version = appium_server_version['build']['version']
139
- if is_sauce
134
+ if sauce?
140
135
  server_version.must_match 'Sauce OnDemand'
141
136
  else
142
- server_version.must_match /(\d+)\.(\d+).(\d+)/
137
+ server_version.must_match(/(\d+)\.(\d+).(\d+)/)
143
138
  end
144
139
  end
145
140
 
@@ -152,12 +147,13 @@ describe 'driver' do
152
147
  driver.browser.must_equal :iOS
153
148
  end
154
149
 
155
- =begin
156
- Skip:
157
- screenshot # this is slow and already tested by Appium
158
- driver_quit # tested by restart
159
- start_driver # tested by restart
160
- =end
150
+ #
151
+ # Skip:
152
+ # screenshot # this is slow and already tested by Appium
153
+ # driver_quit # tested by restart
154
+ # start_driver # tested by restart
155
+ #
156
+
161
157
  t 'set_wait' do
162
158
  # fill the @last_waits array with: [30, 30]
163
159
  set_wait(30).must_equal(30)
@@ -186,7 +182,12 @@ describe 'driver' do
186
182
  # returns true unless an error is raised
187
183
  t 'exists' do
188
184
  exists(0, 0) { true }.must_equal true
189
- exists(0, 0) { raise 'error' }.must_equal false
185
+ exists(0, 0) { fail 'error' }.must_equal false
186
+ end
187
+
188
+ # simple integration sanity test to check for unexpected exceptions
189
+ t 'set_location' do
190
+ set_location latitude: 55, longitude: -72, altitude: 33
190
191
  end
191
192
 
192
193
  # any script
@@ -2,7 +2,11 @@
2
2
  describe 'ios/element/alert' do
3
3
  def nav_once
4
4
  screen.must_equal catalog
5
- wait_true { text('alerts').click; tag('UIANavigationBar').name == 'Alerts' } # wait for true
5
+ wait_true do
6
+ text('alerts').click
7
+ tag('UIANavigationBar').name == 'Alerts' # wait for true
8
+ end
9
+
6
10
  tag('UIANavigationBar').name.must_equal 'Alerts'
7
11
 
8
12
  # redefine method as no-op after it's invoked once
@@ -40,4 +44,4 @@ describe 'ios/element/alert' do
40
44
  t 'after_last' do
41
45
  after_last
42
46
  end
43
- end
47
+ end
@@ -30,7 +30,7 @@ describe 'ios/element/button' do
30
30
  t 'buttons' do
31
31
  exp = ['Back', 'Back', 'Gray', 'Right pointing arrow']
32
32
  target_buttons = buttons('a')
33
- target_buttons.map { |e| e.name }.must_equal exp
33
+ target_buttons.map(&:name).must_equal exp
34
34
  target_buttons.length.must_equal exp.length
35
35
  end
36
36
 
@@ -53,4 +53,4 @@ describe 'ios/element/button' do
53
53
  t 'after_last' do
54
54
  after_last
55
55
  end
56
- end
56
+ end
@@ -8,7 +8,7 @@ describe 'ios/element/generic' do
8
8
  'Buttons'
9
9
  end
10
10
 
11
- def verify element
11
+ def verify(element)
12
12
  element = element.first if element.is_a? Array
13
13
  element.name.must_equal uibutton_text
14
14
  end
@@ -1,3 +1,5 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+
1
3
  # rake ios['ios/element/text']
2
4
  describe 'ios/element/text' do
3
5
  def ui_catalog
@@ -34,7 +36,7 @@ describe 'ios/element/text' do
34
36
  t 'texts' do
35
37
  exp = ['Controls', 'Various uses of UIControl', 'Various uses of UISegmentedControl']
36
38
  texts.length.must_equal 24
37
- texts('trol').map { |e| e.name }.must_equal exp
39
+ texts('trol').map(&:name).must_equal exp
38
40
  texts('uses').length.must_equal 7
39
41
  end
40
42
 
@@ -44,6 +46,7 @@ describe 'ios/element/text' do
44
46
  act = begin
45
47
  text_exact 'mos'
46
48
  rescue
49
+ # nop
47
50
  end
48
51
  act.must_be_nil
49
52
  set_wait
@@ -27,14 +27,14 @@ describe 'ios/element/textfield' do
27
27
  end
28
28
 
29
29
  t 'textfields' do
30
- values = textfields('enter').map { |e| e.value }
30
+ values = textfields('enter').map(&:value)
31
31
  values.include?('<enter text>').must_equal true
32
32
  values.include?('<enter password>').must_equal true
33
33
  textfields.length.must_equal 4
34
34
  end
35
35
 
36
36
  t 'predicate textfields' do
37
- textfield_count = execute_script(%Q(au.mainApp().getAllWithPredicate("type contains[c] 'textfield'", true))).length
37
+ textfield_count = execute_script(%(au.mainApp().getAllWithPredicate("type contains[c] 'textfield'", true))).length
38
38
  textfield_count.must_equal 4
39
39
  end
40
40
 
@@ -72,14 +72,14 @@ describe 'ios/element/textfield' do
72
72
  textfield(1).send_keys 'ok'
73
73
  keyboard_must_exist
74
74
 
75
- # type will dismiss the keyboard
75
+ # type should not dismiss the keyboard
76
76
  message = 'type test type'
77
77
  textfield(1).type message
78
- keyboard_must_not_exist
78
+ keyboard_must_exist
79
79
  textfield(1).text.must_equal message
80
80
  end
81
81
 
82
- def must_raise_no_element &block
82
+ def must_raise_no_element(&block)
83
83
  proc { block.call }.must_raise Selenium::WebDriver::Error::NoSuchElementError
84
84
  end
85
85
 
@@ -123,4 +123,4 @@ describe 'ios/element/textfield' do
123
123
  t 'after_last' do
124
124
  after_last
125
125
  end
126
- end
126
+ end
@@ -13,7 +13,7 @@ describe 'ios/helper' do
13
13
  ios_password(2).must_equal 8226.chr('UTF-8') * 2
14
14
  end
15
15
 
16
- # todo: t 'get_page' do
16
+ # TODO: t 'get_page' do
17
17
 
18
18
  # t 'page' do # writes to std out
19
19
 
@@ -21,7 +21,7 @@ describe 'ios/helper' do
21
21
  source_window.length.must_equal 11
22
22
  end
23
23
 
24
- # todo: t 'page_window' do
25
- # todo: t 'id' do
26
- # todo: t 'ios_version' do
27
- end
24
+ # TODO: t 'page_window' do
25
+ # TODO: t 'id' do
26
+ # TODO: t 'ios_version' do
27
+ end
@@ -13,7 +13,7 @@ describe 'ios/patch' do
13
13
  before_first
14
14
  end
15
15
 
16
- # todo: test 'label'
16
+ # TODO: test 'label'
17
17
 
18
18
  t 'type' do
19
19
  # nav to textfield
@@ -27,4 +27,4 @@ describe 'ios/patch' do
27
27
  t 'after_last' do
28
28
  after_last
29
29
  end
30
- end
30
+ end
@@ -1,4 +1,4 @@
1
1
  # ios tests have a set of common helper methods
2
2
  require_relative 'common'
3
3
  # run file is identical to android
4
- require_relative '../../android_tests/lib/run'
4
+ require_relative '../../android_tests/lib/run'
@@ -12,7 +12,7 @@ ruby_lib's iOS tests. Requires `Ruby 1.9.3` or better.
12
12
 
13
13
  `UICatalog6.1` is from [appium/appium](https://github.com/appium/appium/blob/master/assets/UICatalog6.1.app.zip)
14
14
 
15
- The tests are now run against `iPhone Simulator 7.0.3 (11B508)`
15
+ The tests are now run against `iPhone 6 Simulator 8.3 (12F69)`
16
16
 
17
17
  #### Documentation
18
18
 
@@ -24,7 +24,7 @@ The tests are now run against `iPhone Simulator 7.0.3 (11B508)`
24
24
  --
25
25
 
26
26
  ```java
27
- Finished in 1 min 49 secs
27
+ Finished in 1 min 57 secs
28
28
 
29
- 101 runs, 130 assertions, 0 failures, 0 errors, 0 skips
29
+ 123 runs, 164 assertions, 0 failures, 0 errors, 0 skips
30
30
  ```
@@ -6,13 +6,13 @@ s = SauceStorage.new username: 'my_user_name', key: '00', debug: true
6
6
  # or if you have SAUCE_USERNAME and SAUCE_ACCESS_KEY in env already
7
7
 
8
8
  s = SauceStorage.new debug: true
9
-
9
+
10
10
  # list all files
11
11
  s.files
12
-
12
+
13
13
  # upload a file
14
14
  s.upload '/tmp/sauce/test.zip'
15
-
15
+
16
16
  > s.files
17
17
  => [{"size"=>8,
18
18
  "mtime"=>1367700857.1011374,
@@ -30,20 +30,20 @@ require 'json'
30
30
  class SauceStorage
31
31
  attr_reader :username, :key, :url, :debug
32
32
 
33
- def initialize opts
33
+ def initialize(opts)
34
34
  @username = opts.fetch :username, ENV['SAUCE_USERNAME']
35
35
  @key = opts.fetch :key, ENV['SAUCE_ACCESS_KEY']
36
36
  @url = "https://#{@username}:#{@key}@saucelabs.com/rest/v1/storage/#{@username}"
37
37
  @debug = opts.fetch :debug, false
38
38
  end
39
39
 
40
- def upload file_path
40
+ def upload(file_path)
41
41
  file_name = File.basename file_path
42
42
  file = File.new file_path
43
43
  local_md5 = Digest::MD5.hexdigest File.read file_path
44
44
 
45
- self.files.each do |file|
46
- if file['md5'] == local_md5
45
+ files.each do |f|
46
+ if f['md5'] == local_md5
47
47
  puts 'File already uploaded' if @debug
48
48
  return true
49
49
  end
@@ -62,4 +62,4 @@ class SauceStorage
62
62
  def files
63
63
  JSON.parse(RestClient.get @url)['files']
64
64
  end
65
- end
65
+ end
@@ -3,4 +3,4 @@ require File.expand_path '../sauce_storage', __FILE__
3
3
  s = SauceStorage.new debug: true
4
4
  apk = File.expand_path '../' * 2 + ENV['UPLOAD_FILE'], __FILE__
5
5
 
6
- s.upload apk
6
+ s.upload apk
@@ -2,7 +2,7 @@ require 'nokogiri'
2
2
 
3
3
  module Appium
4
4
  module Android
5
- def _nodeset_to_uiselector opts={}
5
+ def _nodeset_to_uiselector(opts = {})
6
6
  results = ''
7
7
 
8
8
  nodes = opts[:nodes]
@@ -11,16 +11,16 @@ module Appium
11
11
  nodes = [nodes[0]] if first
12
12
 
13
13
  nodes.each do |node|
14
- results += %Q(new UiSelector().className("#{node.name}").instance(#{node.attr('instance')});)
14
+ results += %(new UiSelector().className("#{node.name}").instance(#{node.attr('instance')});)
15
15
  end
16
16
 
17
17
  results.strip
18
18
  end
19
19
 
20
- def _client_xpath opts={}
20
+ def _client_xpath(opts = {})
21
21
  root_node = Nokogiri::XML(get_source).children.first
22
22
 
23
- instance = Hash.new -1
23
+ instance = Hash.new(-1)
24
24
 
25
25
  root_node.traverse do |node|
26
26
  number = instance[node.name] += 1
@@ -33,15 +33,15 @@ module Appium
33
33
  _nodeset_to_uiselector nodes: nodes, first: first
34
34
  end
35
35
 
36
- def client_xpath xpath
36
+ def client_xpath(xpath)
37
37
  find_element :uiautomator, _client_xpath(xpath: xpath, first: true)
38
38
  end
39
39
 
40
- def client_xpaths xpath
40
+ def client_xpaths(xpath)
41
41
  find_elements :uiautomator, _client_xpath(xpath: xpath, first: false)
42
42
  end
43
43
  end
44
44
  end
45
45
 
46
46
  # http://stackoverflow.com/questions/9199415/getting-first-node-in-xpath-result-set
47
- # '(//android.widget.TextView)[1]' not '//android.widget.TextView[1]'
47
+ # '(//android.widget.TextView)[1]' not '//android.widget.TextView[1]'
@@ -3,7 +3,7 @@ module Appium
3
3
  # Click the first alert button that contains value or by index.
4
4
  # @param value [Integer, String] either an integer index of the button or the button's name
5
5
  # @return [void]
6
- def alert_click value
6
+ def alert_click(value)
7
7
  button(value).click
8
8
  end
9
9
 
@@ -35,4 +35,4 @@ module Appium
35
35
  first_button.text
36
36
  end
37
37
  end # module Android
38
- end # module Appium
38
+ end # module Appium
@@ -7,28 +7,28 @@ module Appium
7
7
  private
8
8
 
9
9
  # @private
10
- def _button_visible_selectors opts={}
10
+ def _button_visible_selectors(opts = {})
11
11
  button_index = opts.fetch :button_index, false
12
12
  image_button_index = opts.fetch :image_button_index, false
13
13
 
14
14
  if button_index && image_button_index
15
- "new UiSelector().className(#{Button}).instance(#{button_index});" +
16
- "new UiSelector().className(#{ImageButton}).instance(#{image_button_index});"
15
+ "new UiSelector().className(#{Button}).instance(#{button_index});" \
16
+ "new UiSelector().className(#{ImageButton}).instance(#{image_button_index});"
17
17
  else
18
- "new UiSelector().className(#{Button});" +
19
- "new UiSelector().className(#{ImageButton});"
18
+ "new UiSelector().className(#{Button});" \
19
+ "new UiSelector().className(#{ImageButton});"
20
20
  end
21
21
  end
22
22
 
23
23
  # @private
24
- def _button_exact_string value
24
+ def _button_exact_string(value)
25
25
  button = string_visible_exact Button, value
26
26
  image_button = string_visible_exact ImageButton, value
27
27
  button + image_button
28
28
  end
29
29
 
30
30
  # @private
31
- def _button_contains_string value
31
+ def _button_contains_string(value)
32
32
  button = string_visible_contains Button, value
33
33
  image_button = string_visible_contains ImageButton, value
34
34
  button + image_button
@@ -40,12 +40,12 @@ module Appium
40
40
  # @param value [String, Integer] the value to exactly match.
41
41
  # If int then the button at that index is returned.
42
42
  # @return [Button]
43
- def button value
43
+ def button(value)
44
44
  # Don't use ele_index because that only works on one element type.
45
45
  # Android needs to combine button and image button to match iOS.
46
46
  if value.is_a? Numeric
47
47
  index = value
48
- raise "#{index} is not a valid index. Must be >= 1" if index <= 0
48
+ fail "#{index} is not a valid index. Must be >= 1" if index <= 0
49
49
 
50
50
  return find_element :uiautomator, _button_visible_selectors(index: index)
51
51
  end
@@ -57,7 +57,7 @@ module Appium
57
57
  # If value is omitted, all buttons are returned.
58
58
  # @param value [String] the value to search for
59
59
  # @return [Array<Button>]
60
- def buttons value=false
60
+ def buttons(value = false)
61
61
  return find_elements :uiautomator, _button_visible_selectors unless value
62
62
  find_elements :uiautomator, _button_contains_string(value)
63
63
  end
@@ -73,28 +73,28 @@ module Appium
73
73
  def last_button
74
74
  # uiautomator index doesn't support last
75
75
  # and it's 0 indexed
76
- button_index = tags(Button).length
77
- button_index -= 1 if button_index > 0
76
+ button_index = tags(Button).length
77
+ button_index -= 1 if button_index > 0
78
78
  image_button_index = tags(ImageButton).length
79
79
  image_button_index -= 1 if image_button_index > 0
80
80
 
81
81
  find_element :uiautomator,
82
82
  _button_visible_selectors(button_index: button_index,
83
- image_button_index: image_button_index)
83
+ image_button_index: image_button_index)
84
84
  end
85
85
 
86
86
  # Find the first button that exactly matches value.
87
87
  # @param value [String] the value to match exactly
88
88
  # @return [Button]
89
- def button_exact value
89
+ def button_exact(value)
90
90
  find_element :uiautomator, _button_exact_string(value)
91
91
  end
92
92
 
93
93
  # Find all buttons that exactly match value.
94
94
  # @param value [String] the value to match exactly
95
95
  # @return [Array<Button>]
96
- def buttons_exact value
96
+ def buttons_exact(value)
97
97
  find_elements :uiautomator, _button_exact_string(value)
98
98
  end
99
99
  end # module Android
100
- end # module Appium
100
+ end # module Appium