appium_lib 3.0.3 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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