appium_lib 3.0.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/android_tests/Rakefile +0 -3
  3. data/android_tests/appium.txt +1 -0
  4. data/android_tests/lib/android/specs/android/element/alert.rb +9 -2
  5. data/android_tests/lib/android/specs/android/element/textfield.rb +5 -0
  6. data/android_tests/lib/android/specs/android/helper.rb +6 -15
  7. data/android_tests/lib/android/specs/android/patch.rb +16 -3
  8. data/android_tests/lib/android/specs/common/device.rb +41 -41
  9. data/android_tests/lib/android/specs/common/patch.rb +1 -1
  10. data/android_tests/lib/android/specs/common/web_context.rb +55 -7
  11. data/android_tests/lib/android/specs/driver.rb +4 -3
  12. data/android_tests/lib/android/specs/install.rb +25 -0
  13. data/android_tests/lib/run.rb +4 -3
  14. data/docs/android_docs.md +357 -187
  15. data/docs/docs.md +8 -28
  16. data/docs/ios_docs.md +341 -179
  17. data/docs/migration.md +6 -0
  18. data/ios_tests/Rakefile +0 -3
  19. data/ios_tests/appium.txt +1 -0
  20. data/ios_tests/lib/common.rb +30 -0
  21. data/ios_tests/lib/ios/specs/device/device.rb +6 -1
  22. data/ios_tests/lib/ios/specs/driver.rb +3 -3
  23. data/ios_tests/lib/ios/specs/ios/element/alert.rb +0 -5
  24. data/ios_tests/lib/ios/specs/ios/element/textfield.rb +10 -0
  25. data/ios_tests/lib/run.rb +4 -106
  26. data/lib/appium_lib.rb +2 -2
  27. data/lib/appium_lib/android/element/button.rb +16 -26
  28. data/lib/appium_lib/android/element/generic.rb +15 -12
  29. data/lib/appium_lib/android/helper.rb +39 -53
  30. data/lib/appium_lib/common/patch.rb +10 -39
  31. data/lib/appium_lib/common/version.rb +2 -2
  32. data/lib/appium_lib/device/device.rb +54 -70
  33. data/lib/appium_lib/driver.rb +29 -19
  34. data/lib/appium_lib/ios/helper.rb +50 -1
  35. data/lib/appium_lib/ios/patch.rb +1 -23
  36. data/release_notes.md +38 -0
  37. metadata +4 -4
  38. data/android_tests/lib/android/specs/android/dynamic.rb +0 -5
  39. data/lib/appium_lib/android/dynamic.rb +0 -47
@@ -1,3 +1,9 @@
1
+ ### Breaking Changes in 4.0
2
+
3
+ Old | New
4
+ :--|:--
5
+ `key_event` | `press_keycode`
6
+
1
7
  ### Breaking Changes in 2.0
2
8
 
3
9
  In 2.0, the e_* methods have been renamed.
@@ -36,9 +36,6 @@ task :ios, :args, :test_file do |args, test_file|
36
36
  # args = android
37
37
  # test_file = {:args=>"ok"}
38
38
  test_file = test_file[:args]
39
- path = File.expand_path('appium.txt', Rake.application.original_dir)
40
- ENV['APPIUM_TXT'] = path
41
- puts "Rake appium.txt path is: #{path}"
42
39
  cmd = 'bundle exec ruby ./lib/run.rb ios'
43
40
  cmd += %Q( "#{test_file}") if test_file
44
41
  bash cmd
@@ -1,5 +1,6 @@
1
1
  [caps]
2
2
  platformName = "ios"
3
+ deviceName ="iPhone Simulator"
3
4
  app = "./UICatalog.app"
4
5
 
5
6
  [appium_lib]
@@ -0,0 +1,30 @@
1
+
2
+ # common methods
3
+ def back_click(opts={})
4
+ opts ||= {}
5
+ search_wait = opts.fetch(:wait, 60 * 1.7)
6
+ # iOS may have multiple 'back' buttons
7
+ # select the first displayed? back button.
8
+ wait(search_wait) do
9
+ button_exact('Back').click
10
+ end
11
+ end
12
+
13
+ def leave_textfields
14
+ back_click
15
+ screen.must_equal catalog
16
+ end
17
+
18
+ def go_to_textfields
19
+ screen.must_equal catalog
20
+ wait_true { text('textfield').click; screen == 'TextFields' } # wait for screen transition
21
+ screen.must_equal 'TextFields'
22
+ end
23
+
24
+ def screen
25
+ $driver.find_element(:class, 'UIANavigationBar').name
26
+ end
27
+
28
+ def catalog
29
+ 'UICatalog'
30
+ end
@@ -76,8 +76,13 @@ describe 'device/device' do
76
76
  pinch 75
77
77
  end
78
78
 
79
- t 'file movement' do
79
+ t 'pull_file' do
80
80
  read_file = pull_file 'Library/AddressBook/AddressBook.sqlitedb'
81
81
  read_file.start_with?('SQLite format').must_equal true
82
82
  end
83
+
84
+ t 'pull_folder' do
85
+ data = pull_folder 'Library/AddressBook'
86
+ data.length.must_be :>, 1
87
+ end
83
88
  end
@@ -21,9 +21,8 @@ describe 'driver' do
21
21
  # skip this test if we're using Sauce
22
22
  # the storage API doesn't have an on disk file
23
23
  skip if is_sauce
24
- # __FILE__ is '(eval)' so use env var set by the Rakefile
25
- path = ENV['APPIUM_TXT']
26
- opts = Appium.load_appium_txt file: path, verbose: true
24
+ appium_txt = File.expand_path(File.join(Dir.pwd, 'lib'))
25
+ opts = Appium.load_appium_txt file: appium_txt, verbose: true
27
26
 
28
27
  actual = ''
29
28
  actual = File.basename opts[:caps][:app] if opts && opts[:caps]
@@ -37,6 +36,7 @@ describe 'driver' do
37
36
  actual = driver_attributes
38
37
  actual[:caps][:app] = File.basename actual[:caps][:app]
39
38
  expected = { caps: { platformName: 'ios',
39
+ deviceName: 'iPhone Simulator',
40
40
  app: 'UICatalog.app' },
41
41
  custom_url: false,
42
42
  export_session: false,
@@ -29,11 +29,6 @@ describe 'ios/element/alert' do
29
29
  end
30
30
  end
31
31
 
32
- # iOS 7 is not using the alert methods. alert is nil.
33
- def ios7_alert_detected
34
- execute_script 'UIATarget.localTarget().frontMostApp().alert().isNil()'
35
- end
36
-
37
32
  t 'alert_accept' do
38
33
  alert_accept
39
34
  end
@@ -33,6 +33,11 @@ describe 'ios/element/textfield' do
33
33
  textfields.length.must_equal 4
34
34
  end
35
35
 
36
+ t 'predicate textfields' do
37
+ textfield_count = execute_script(%Q(au.mainApp().getAllWithPredicate("type contains[c] 'textfield'", true))).length
38
+ textfield_count.must_equal 4
39
+ end
40
+
36
41
  t 'first_textfield' do
37
42
  first_textfield.text.must_equal enter_text
38
43
  end
@@ -110,6 +115,11 @@ describe 'ios/element/textfield' do
110
115
  textfields_exact('does not exist').length.must_equal 0
111
116
  end
112
117
 
118
+ t 'hide_keyboard' do
119
+ first_textfield.click
120
+ hide_keyboard
121
+ end
122
+
113
123
  t 'after_last' do
114
124
  after_last
115
125
  end
@@ -1,106 +1,4 @@
1
- require 'rubygems'
2
- require 'spec'
3
- require 'hashdiff'
4
-
5
- require_relative '../../lib/appium_lib'
6
-
7
- =begin
8
- Run all Android tests:
9
- ruby run.rb android
10
-
11
- Run only the view album test:
12
- ruby run.rb ios view_album
13
- =end
14
-
15
- # Sanity check
16
- a = OpenStruct.new x: 'ok'
17
- raise 'x issue' unless a.x == 'ok'
18
-
19
- # common methods
20
- def back_click(opts={})
21
- opts ||= {}
22
- search_wait = opts.fetch(:wait, 60 * 1.7)
23
- # iOS may have multiple 'back' buttons
24
- # select the first displayed? back button.
25
- wait(search_wait) do
26
- button_exact('Back').click
27
- end
28
- end
29
-
30
- def leave_textfields
31
- back_click
32
- screen.must_equal catalog
33
- end
34
-
35
- def go_to_textfields
36
- screen.must_equal catalog
37
- wait_true { text('textfield').click; screen == 'TextFields' } # wait for screen transition
38
- screen.must_equal 'TextFields'
39
- end
40
-
41
- def screen
42
- $driver.find_element(:class, 'UIANavigationBar').name
43
- end
44
-
45
- def catalog
46
- 'UICatalog'
47
- end
48
-
49
- ##
50
-
51
- caps = Appium.load_appium_txt file: File.expand_path('..', __FILE__), verbose: true
52
- caps = caps.merge({ appium_lib: { debug: true, wait: 30 } })
53
-
54
- dir = File.expand_path '..', __FILE__
55
- device = ARGV[0].downcase.strip
56
- devices = %w[ android selendroid ios ]
57
- raise 'Expected android, selendroid or ios as first argument' unless devices.include? device
58
-
59
- one_test = ARGV[1]
60
- test_dir = "/#{device}/"
61
-
62
- caps[:app] = ENV['SAUCE_PATH'] if ENV['SAUCE_USERNAME'] && ENV['SAUCE_ACCESS_KEY']
63
-
64
- trace_files = []
65
-
66
- if one_test
67
- unless File.exists? one_test
68
- # ensure ext is .rb
69
- one_test = File.join(File.dirname(one_test),
70
- File.basename(one_test, '.*') + '.rb')
71
- one_test = File.join(dir, test_dir + 'specs/', one_test)
72
- else
73
- one_test = File.expand_path one_test
74
- end
75
- raise "\nTest #{one_test} does not exist.\n" unless File.exists?(one_test)
76
- Appium::Driver.new(caps).start_driver
77
- # require support (common.rb)
78
- Dir.glob(File.join dir, test_dir + '/*.rb') do |test|
79
- require test
80
- trace_files << test
81
- end
82
- puts "Loading one test: #{one_test}"
83
- require one_test
84
- trace_files << one_test
85
- else
86
- # require all
87
- Dir.glob(File.join dir, test_dir + '**/*.rb') do |test|
88
- # load all tests
89
- trace_files << test
90
- puts " #{File.basename(test, '.*')}"
91
- require test
92
- end
93
- Appium::Driver.new(caps).start_driver
94
- end
95
-
96
- trace_files.map! do |f|
97
- f = File.expand_path f
98
- # ensure all traced files end in .rb
99
- f = File.join(File.dirname(f), File.basename(f, '.*') + '.rb')
100
- f
101
- end
102
-
103
- # Exit after tests.
104
- Minitest.after_run { $driver.x if $driver }
105
- # Run Minitest. Provide test file array for tracing.
106
- Minitest.run_specs({ :trace => trace_files })
1
+ # ios tests have a set of common helper methods
2
+ require_relative 'common'
3
+ # run file is identical to android
4
+ require_relative '../../android_tests/lib/run'
@@ -2,7 +2,7 @@
2
2
  Encoding.default_external = Encoding::UTF_8
3
3
  Encoding.default_internal = Encoding::UTF_8
4
4
 
5
- require 'Forwardable' unless defined? Forwardable
5
+ require 'forwardable' unless defined? Forwardable
6
6
  require_relative 'appium_lib/rails/duplicable'
7
7
 
8
8
  $driver = nil
@@ -25,4 +25,4 @@ def self.method_missing method, *args, &block
25
25
  end
26
26
 
27
27
  require_relative 'appium_lib/logger'
28
- require_relative 'appium_lib/driver'
28
+ require_relative 'appium_lib/driver'
@@ -11,24 +11,12 @@ module Appium
11
11
  button_index = opts.fetch :button_index, false
12
12
  image_button_index = opts.fetch :image_button_index, false
13
13
 
14
- # complex_find(...)
15
- # 4 = className(String className)
16
- # 9 = instance(final int instance)
17
-
18
14
  if button_index && image_button_index
19
- [
20
- # className().instance()
21
- [[4, Button], [9, button_index]],
22
- # className().instance()
23
- [[4, ImageButton], [9, image_button_index]]
24
- ]
15
+ "new UiSelector().className(#{Button}).instance(#{button_index});" +
16
+ "new UiSelector().className(#{ImageButton}).instance(#{image_button_index});"
25
17
  else
26
- [
27
- # className()
28
- [[4, Button]],
29
- # className()
30
- [[4, ImageButton]]
31
- ]
18
+ "new UiSelector().className(#{Button});" +
19
+ "new UiSelector().className(#{ImageButton});"
32
20
  end
33
21
  end
34
22
 
@@ -59,10 +47,10 @@ module Appium
59
47
  index = value
60
48
  raise "#{index} is not a valid index. Must be >= 1" if index <= 0
61
49
 
62
- return complex_find _button_visible_selectors index: index
50
+ return find_element :uiautomator, _button_visible_selectors(index: index)
63
51
  end
64
52
 
65
- complex_find _button_contains_string value
53
+ find_element :uiautomator, _button_contains_string(value)
66
54
  end
67
55
 
68
56
  # Find all buttons containing value.
@@ -70,14 +58,14 @@ module Appium
70
58
  # @param value [String] the value to search for
71
59
  # @return [Array<Button>]
72
60
  def buttons value=false
73
- return complex_find mode: 'all', selectors: _button_visible_selectors unless value
74
- complex_find mode: 'all', selectors: _button_contains_string(value)
61
+ return find_elements :uiautomator, _button_visible_selectors unless value
62
+ find_elements :uiautomator, _button_contains_string(value)
75
63
  end
76
64
 
77
65
  # Find the first button.
78
66
  # @return [Button]
79
67
  def first_button
80
- complex_find _button_visible_selectors button_index: 0, image_button_index: 0
68
+ find_element :uiautomator, _button_visible_selectors(button_index: 0, image_button_index: 0)
81
69
  end
82
70
 
83
71
  # Find the last button.
@@ -86,25 +74,27 @@ module Appium
86
74
  # uiautomator index doesn't support last
87
75
  # and it's 0 indexed
88
76
  button_index = tags(Button).length
89
- button_index -= 1 if button_index >= 0
77
+ button_index -= 1 if button_index > 0
90
78
  image_button_index = tags(ImageButton).length
91
- image_button_index -= 1 if image_button_index >= 0
79
+ image_button_index -= 1 if image_button_index > 0
92
80
 
93
- complex_find _button_visible_selectors button_index: button_index, image_button_index: image_button_index
81
+ find_element :uiautomator,
82
+ _button_visible_selectors(button_index: button_index,
83
+ image_button_index: image_button_index)
94
84
  end
95
85
 
96
86
  # Find the first button that exactly matches value.
97
87
  # @param value [String] the value to match exactly
98
88
  # @return [Button]
99
89
  def button_exact value
100
- complex_find _button_exact_string value
90
+ find_element :uiautomator, _button_exact_string(value)
101
91
  end
102
92
 
103
93
  # Find all buttons that exactly match value.
104
94
  # @param value [String] the value to match exactly
105
95
  # @return [Array<Button>]
106
96
  def buttons_exact value
107
- complex_find mode: 'all', selectors: _button_exact_string(value)
97
+ find_elements :uiautomator, _button_exact_string(value)
108
98
  end
109
99
  end # module Android
110
100
  end # module Appium
@@ -29,30 +29,33 @@ module Appium
29
29
  complex_finds_exact '*', value
30
30
  end
31
31
 
32
+ # @private
33
+ def scroll_uiselector content
34
+ "new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(#{content}.instance(0));"
35
+ end
36
+
32
37
  # Scroll to the first element containing target text or description.
33
38
  # @param text [String] the text to search for in the text value and content description
34
39
  # @return [Element] the element scrolled to
35
40
  def scroll_to text
36
- args =
37
- # textContains(text)
38
- [[3, text]],
39
- # descriptionContains(text)
40
- [[7, text]]
41
+ text = %Q("#{text}")
42
+
43
+ args = scroll_uiselector("new UiSelector().textContains(#{text})") +
44
+ scroll_uiselector("new UiSelector().descriptionContains(#{text})")
41
45
 
42
- complex_find mode: 'scroll', selectors: args
46
+ find_element :uiautomator, args
43
47
  end
44
48
 
45
49
  # Scroll to the first element with the exact target text or description.
46
50
  # @param text [String] the text to search for in the text value and content description
47
51
  # @return [Element] the element scrolled to
48
52
  def scroll_to_exact text
49
- args =
50
- # text(text)
51
- [[1, text]],
52
- # description(text)
53
- [[5, text]]
53
+ text = %Q("#{text}")
54
+
55
+ args = scroll_uiselector("new UiSelector().text(#{text})") +
56
+ scroll_uiselector("new UiSelector().description(#{text})")
54
57
 
55
- complex_find mode: 'scroll', selectors: args
58
+ find_element :uiautomator, args
56
59
  end
57
60
  end # module Android
58
61
  end # module Appium
@@ -3,8 +3,7 @@ module Appium
3
3
  # @private
4
4
  # http://nokogiri.org/Nokogiri/XML/SAX.html
5
5
  class AndroidElements < Nokogiri::XML::SAX::Document
6
- # TODO: Support strings.xml ids
7
- attr_reader :result, :keys
6
+ attr_reader :result, :keys, :instance
8
7
 
9
8
  def filter
10
9
  @filter
@@ -20,17 +19,22 @@ module Appium
20
19
  def initialize
21
20
  reset
22
21
  @filter = false
22
+ @instance = Hash.new -1
23
23
  end
24
24
 
25
25
  def reset
26
26
  @result = ''
27
27
  @keys = %w[text resource-id content-desc]
28
+ @instance = Hash.new -1
28
29
  end
29
30
 
30
31
  # http://nokogiri.org/Nokogiri/XML/SAX/Document.html
31
32
  def start_element name, attrs = []
32
33
  return if filter && !name.downcase.include?(filter)
33
34
 
35
+ # instance numbers start at 0.
36
+ number = instance[name] += 1
37
+
34
38
  attributes = {}
35
39
 
36
40
  attrs.each do |key, value|
@@ -75,7 +79,7 @@ module Appium
75
79
  string += " id: #{id}\n" unless id.nil?
76
80
  string += " strings.xml: #{string_ids}" unless string_ids.nil?
77
81
 
78
- @result += "\n#{name}\n#{string}" unless attributes.empty?
82
+ @result += "\n#{name} (#{number})\n#{string}" unless attributes.empty?
79
83
  end
80
84
  end # class AndroidElements
81
85
 
@@ -92,10 +96,12 @@ module Appium
92
96
  else
93
97
  parser = @android_webview_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new)
94
98
  end
95
- parser.document.reset
99
+ parser.document.reset # ensure document is reset before parsing
96
100
  parser.document.filter = class_name
97
101
  parser.parse source
98
- parser.document.result
102
+ result = parser.document.result
103
+ parser.document.reset # clean up any created objects after parsing
104
+ result
99
105
  end
100
106
 
101
107
  # Intended for use with console.
@@ -157,14 +163,18 @@ module Appium
157
163
  # @param index [Integer] the index
158
164
  # @return [Element] the found element of type class_name
159
165
  def ele_index class_name, index
166
+ results = tags(class_name)
160
167
  if index == 'last()'
161
- index = tags(class_name).length
168
+ index = results.length
162
169
  index -= 1 if index >= 0
163
170
  else
164
171
  raise 'Index must be >= 1' unless index >= 1
165
172
  index -= 1 if index >= 1
166
173
  end
167
- complex_find [[[4, class_name], [9, index]]]
174
+
175
+ # uiautomator has issues with index/instance so calculate the index
176
+ # client side.
177
+ results[index]
168
178
  end
169
179
 
170
180
  # Find the first element that matches class_name
@@ -206,32 +216,19 @@ module Appium
206
216
  # @param value [String] the value to search for
207
217
  # @return [String]
208
218
  def string_visible_contains class_name, value
209
- # 4 = className(String className)
210
- # 29 = resourceId(String id
211
- # 7 = descriptionContains(String desc)
212
- # 3 = textContains(String text)
213
- # todo: textContains isn't case insensitive
214
- # descriptionContains is case insensitive
219
+ value = %Q("#{value}")
215
220
 
216
221
  if class_name == '*'
217
- return [
218
- # resourceId()
219
- [[29, value]],
220
- # descriptionContains()
221
- [[7, value]],
222
- # textContains()
223
- [[3, value]]
224
- ]
222
+ return "new UiSelector().resourceId(#{value});" +
223
+ "new UiSelector().descriptionContains(#{value});" +
224
+ "new UiSelector().textContains(#{value});"
225
225
  end
226
226
 
227
- [
228
- # className().resourceId()
229
- [[4, class_name], [29, value]],
230
- # className().descriptionContains()
231
- [[4, class_name], [7, value]],
232
- # className().textContains()
233
- [[4, class_name], [3, value]]
234
- ]
227
+ class_name = %Q("#{class_name}")
228
+
229
+ "new UiSelector().className(#{class_name}).resourceId(#{value});" +
230
+ "new UiSelector().className(#{class_name}).descriptionContains(#{value});" +
231
+ "new UiSelector().className(#{class_name}).textContains(#{value});"
235
232
  end
236
233
 
237
234
  # Find the first element that contains value
@@ -239,7 +236,7 @@ module Appium
239
236
  # @param value [String] the value to search for
240
237
  # @return [Element]
241
238
  def complex_find_contains element, value
242
- complex_find string_visible_contains element, value
239
+ find_element :uiautomator, string_visible_contains(element, value)
243
240
  end
244
241
 
245
242
  # Find all elements containing value
@@ -247,7 +244,7 @@ module Appium
247
244
  # @param value [String] the value to search for
248
245
  # @return [Array<Element>]
249
246
  def complex_finds_contains element, value
250
- complex_find mode: 'all', selectors: string_visible_contains(element, value)
247
+ find_elements :uiautomator, string_visible_contains(element, value)
251
248
  end
252
249
 
253
250
  # @private
@@ -256,30 +253,19 @@ module Appium
256
253
  # @param value [String] the value to search for
257
254
  # @return [String]
258
255
  def string_visible_exact class_name, value
259
- # 4 = className(String className)
260
- # 29 = resourceId(String id
261
- # 5 = description(String desc)
262
- # 1 = text(String text)
256
+ value = %Q("#{value}")
263
257
 
264
258
  if class_name == '*'
265
- return [
266
- # resourceId()
267
- [[29, value]],
268
- # description()
269
- [[5, value]],
270
- # text()
271
- [[1, value]]
272
- ]
259
+ return "new UiSelector().resourceId(#{value});" +
260
+ "new UiSelector().description(#{value});" +
261
+ "new UiSelector().text(#{value});"
273
262
  end
274
263
 
275
- [
276
- # className().resourceId()
277
- [[4, class_name], [29, value]],
278
- # className().description()
279
- [[4, class_name], [5, value]],
280
- # className().text()
281
- [[4, class_name], [1, value]]
282
- ]
264
+ class_name = %Q("#{class_name}")
265
+
266
+ "new UiSelector().className(#{class_name}).resourceId(#{value});" +
267
+ "new UiSelector().className(#{class_name}).description(#{value});" +
268
+ "new UiSelector().className(#{class_name}).text(#{value});"
283
269
  end
284
270
 
285
271
  # Find the first element exactly matching value
@@ -287,7 +273,7 @@ module Appium
287
273
  # @param value [String] the value to search for
288
274
  # @return [Element]
289
275
  def complex_find_exact class_name, value
290
- complex_find string_visible_exact class_name, value
276
+ find_element :uiautomator, string_visible_exact(class_name, value)
291
277
  end
292
278
 
293
279
  # Find all elements exactly matching value
@@ -295,7 +281,7 @@ module Appium
295
281
  # @param value [String] the value to search for
296
282
  # @return [Element]
297
283
  def complex_finds_exact class_name, value
298
- complex_find mode: 'all', selectors: string_visible_exact(class_name, value)
284
+ find_elements :uiautomator, string_visible_exact(class_name, value)
299
285
  end
300
286
  end # module Android
301
287
  end # module Appium