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
@@ -89,47 +89,18 @@ def patch_webdriver_bridge
89
89
  path_str = path.sub(path_match[0], '') unless path_match.nil?
90
90
 
91
91
  puts "#{verb} #{path_str}"
92
- unless command_hash.nil? || command_hash.length == 0
93
- print_command = {}
94
- # For complex_find, we pass a whole array
95
- if command_hash.kind_of? Array
96
- print_command[:args] = [command_hash.clone]
97
- print_command[:script] = 'complex_find' if command == :complex_find
98
- else
99
- print_command = command_hash.clone
100
- end
92
+ # must check to see if command_hash is a hash. sometimes it's not.
93
+ if command_hash.is_a?(Hash) && !command_hash.empty?
94
+ print_command = command_hash.clone
95
+
101
96
  print_command.delete :args if print_command[:args] == []
102
97
 
103
- mobile_find = 'mobile: find'
104
- if print_command[:script] == mobile_find
105
- args = print_command[:args]
106
- puts "#{mobile_find}" #" #{args}"
107
-
108
- # [[[[3, "sign"]]]] => [[[3, "sign"]]]
109
- #
110
- # [[[[4, "android.widget.EditText"], [7, "z"]], [[4, "android.widget.EditText"], [3, "z"]]]]
111
- # => [[[4, "android.widget.EditText"], [7, "z"]], [[4, "android.widget.EditText"], [3, "z"]]]
112
- args = args[0]
113
- option = args[0].to_s.downcase
114
- has_option = !option.match(/all|scroll/).nil?
115
- puts option if has_option
116
-
117
- start = has_option ? 1 : 0
118
-
119
- start.upto(args.length-1) do |selector_index|
120
- selectors = args[selector_index]
121
- selectors_size = selectors.length
122
- selectors.each_index do |pair_index|
123
- pair = selectors[pair_index]
124
- res = $driver.dynamic_code_to_string pair[0], pair[1]
125
-
126
- if selectors_size == 1 || pair_index >= selectors_size - 1
127
- puts res
128
- elsif selectors_size > 1 && pair_index < selectors_size
129
- print res + '.'
130
- end
131
- end
132
- end # start.upto
98
+ if print_command[:using] === '-android uiautomator'
99
+ value = print_command[:value].split(';').map { |v| "#{v};" }
100
+ print_command[:value] = value.length == 1 ? value[0] : value
101
+
102
+ # avoid backslash escape quotes in strings. "\"a\"" => "a"
103
+ puts print_command.ai.gsub('\"', '"')
133
104
  else
134
105
  ap print_command
135
106
  end
@@ -1,5 +1,5 @@
1
1
  module Appium
2
2
  # Version and Date are defined on the 'Appium' module, not 'Appium::Common'
3
- VERSION = '3.0.3' unless defined? ::Appium::VERSION
4
- DATE = '2014-06-02' unless defined? ::Appium::DATE
3
+ VERSION = '4.0.0' unless defined? ::Appium::VERSION
4
+ DATE = '2014-07-05' unless defined? ::Appium::DATE
5
5
  end
@@ -6,6 +6,7 @@ module Appium
6
6
 
7
7
  NoArgMethods = {
8
8
  post: {
9
+ open_notifications: 'session/:session_id/appium/device/open_notifications',
9
10
  shake: 'session/:session_id/appium/device/shake',
10
11
  launch: 'session/:session_id/appium/app/launch',
11
12
  close_app: 'session/:session_id/appium/app/close',
@@ -15,7 +16,6 @@ module Appium
15
16
  get: {
16
17
  current_activity: 'session/:session_id/appium/device/current_activity',
17
18
  current_context: 'session/:session_id/context',
18
- available_contexts: 'session/:session_id/contexts',
19
19
  }
20
20
  }
21
21
 
@@ -44,18 +44,6 @@ module Appium
44
44
  # @!method toggle_flight_mode
45
45
  # toggle flight mode on or off
46
46
 
47
- #@!method complex_find
48
- # Find an element by a complex array of criteria. Available criteria
49
- # are listed in [link here]. Criteria are formed by creating an array
50
- # of arrays, each containing a selector and that selector's value.
51
- #
52
- # ```ruby
53
- # complex_find [[[2, 'Sau'], [14, true]]] # => Find a clickable element
54
- # # whose names starts with 'Sau'
55
- # ```
56
- # @param mod (Symbol) If present, will be the 0th element in the selector array.
57
- # @param selectors (Array<Object>) The selectors to find elements with.
58
-
59
47
  # @!method hide_keyboard
60
48
  # Hide the onscreen keyboard
61
49
  # @param close_key (String) the name of the key which closes the keyboard.
@@ -65,10 +53,17 @@ module Appium
65
53
  # hide_keyboard('Finished') # Close a keyboard with the 'Finished' button
66
54
  # ```
67
55
 
68
- # @!method key_event
69
- # Send a key event to the device.
70
- # @param key (integer) The key to send.
71
- # @param metastate (String) The state the metakeys should be in when sending the key.
56
+ # @!method press_keycode
57
+ # Press keycode on the device.
58
+ # http://developer.android.com/reference/android/view/KeyEvent.html
59
+ # @param key (integer) The key to press.
60
+ # @param metastate (String) The state the metakeys should be in when pressing the key.
61
+
62
+ # @!method long_press_keycode
63
+ # Long press keycode on the device.
64
+ # http://developer.android.com/reference/android/view/KeyEvent.html
65
+ # @param key (integer) The key to long press.
66
+ # @param metastate (String) The state the metakeys should be in when long pressing the key.
72
67
 
73
68
  # @!method push_file
74
69
  # Place a file in a specific location on the device.
@@ -85,6 +80,14 @@ module Appium
85
80
  # pull_file 'Shenanigans.app/some/file' #=> Get 'some/file' from the install location of Shenanigans.app
86
81
  # ```
87
82
 
83
+ # @!method pull_folder
84
+ # Retrieve a folder from the device.
85
+ # @param path (String) absolute path to the folder
86
+ #
87
+ # ```ruby
88
+ # pull_folder '/data/local/tmp' #=> Get the folder at that path
89
+ # ```
90
+
88
91
  # @!method end_coverage
89
92
  # Android only; Ends the test coverage and writes the results to the given path on device.
90
93
  # @param path (String) Path on the device to write too.
@@ -97,6 +100,13 @@ module Appium
97
100
  pair.each_pair { |command, path| add_endpoint_method command, path, verb }
98
101
  end
99
102
 
103
+ add_endpoint_method(:available_contexts, 'session/:session_id/contexts', :get) do
104
+ def available_contexts
105
+ # return empty array instead of nil on failure
106
+ execute(:available_contexts, {}) || []
107
+ end
108
+ end
109
+
100
110
  add_endpoint_method(:app_strings, 'session/:session_id/appium/app/strings') do
101
111
  def app_strings language=nil
102
112
  opts = language ? { language: language } : {}
@@ -141,16 +151,30 @@ module Appium
141
151
  end
142
152
 
143
153
  add_endpoint_method(:hide_keyboard, 'session/:session_id/appium/device/hide_keyboard') do
144
- def hide_keyboard(close_key='Done')
145
- execute :hide_keyboard, {}, keyName: close_key
154
+ def hide_keyboard(close_key=nil)
155
+ # Android can only tapOutside.
156
+ if $driver.device_is_android?
157
+ return execute :hide_keyboard, {}, { strategy: :tapOutside }
158
+ end
159
+
160
+ close_key ||= 'Done' # default to Done key.
161
+ $driver.hide_ios_keyboard close_key
162
+ end
163
+ end
164
+
165
+ add_endpoint_method(:press_keycode, 'session/:session_id/appium/device/press_keycode') do
166
+ def press_keycode(key, metastate=nil)
167
+ args = { keycode: key }
168
+ args[:metastate] = metastate if metastate
169
+ execute :press_keycode, {}, args
146
170
  end
147
171
  end
148
172
 
149
- add_endpoint_method(:key_event, 'session/:session_id/appium/device/keyevent') do
150
- def key_event(key, metastate=nil)
173
+ add_endpoint_method(:long_press_keycode, 'session/:session_id/appium/device/long_press_keycode') do
174
+ def long_press_keycode(key, metastate=nil)
151
175
  args = { keycode: key }
152
176
  args[:metastate] = metastate if metastate
153
- execute :key_event, {}, args
177
+ execute :long_press_keycode, {}, args
154
178
  end
155
179
  end
156
180
 
@@ -161,30 +185,6 @@ module Appium
161
185
  end
162
186
  end
163
187
 
164
- add_endpoint_method(:complex_find, 'session/:session_id/appium/app/complex_find') do
165
- def complex_find(opts)
166
- # allow: complex_find([[[3, 'app']]])
167
- opts = { selectors: opts } if opts.is_a?(Array)
168
- mode = opts.fetch :mode, false # mode can be 'all' or 'scroll'
169
- selectors = opts.fetch :selectors, false
170
- raise 'Complex find must have selectors' unless selectors
171
- selectors = selectors.dup.insert(0, mode) if mode # must dupe before insert
172
-
173
- ids = execute :complex_find, {}, selectors
174
- ids = [ids] unless ids.is_a? Array # wrap single result in an array
175
-
176
- # mode can be set directly in the selectors
177
- find_all = mode == 'all' || selectors.first == 'all'
178
-
179
- result = ids.map do |id|
180
- resolved_id = element_id_from(id)
181
- Selenium::WebDriver::Element.new self, resolved_id
182
- end
183
- # when not finding all, don't return an array
184
- return find_all ? result : result.first
185
- end
186
- end
187
-
188
188
  add_endpoint_method(:push_file, 'session/:session_id/appium/device/push_file') do
189
189
  def push_file(path, filedata)
190
190
  encoded_data = Base64.encode64 filedata
@@ -199,6 +199,14 @@ module Appium
199
199
  end
200
200
  end
201
201
 
202
+ # TODO TEST ME
203
+ add_endpoint_method(:pull_folder, 'session/:session_id/appium/device/pull_folder') do
204
+ def pull_folder(path)
205
+ data = execute :pull_folder, {}, path: path
206
+ Base64.decode64 data
207
+ end
208
+ end
209
+
202
210
  # TODO TEST ME
203
211
  add_endpoint_method(:end_coverage, 'session/:session_id/appium/app/end_test_coverage') do
204
212
  def end_coverage(path, intent)
@@ -247,12 +255,6 @@ module Appium
247
255
 
248
256
  # @private
249
257
  def create_bridge_command(method, verb, path)
250
- # Don't clobber methods that are moved into Selenium
251
- if selenium_has method
252
- log_reimplemented_warning(method, path)
253
- return
254
- end
255
-
256
258
  Selenium::WebDriver::Remote::Bridge.class_eval do
257
259
  command method, verb, path
258
260
  if block_given?
@@ -263,24 +265,6 @@ module Appium
263
265
  end
264
266
  end
265
267
 
266
- # @private
267
- def selenium_has(method)
268
- Selenium::WebDriver::Remote::Bridge.method_defined? method
269
- end
270
-
271
- # @private
272
- def log_reimplemented_warning(method, path)
273
- msg = "Selenium::WebDriver has now implemented the `#{method}` method."
274
- if Selenium::WebDriver::Remote::COMMANDS[method][1] == path
275
- msg << " It may no longer function as expected"
276
- else
277
- msg << " It no longer uses the same endpoint,"
278
- msg << " so it probably won't do what you expect anymore."
279
- end
280
- msg << " Raise an issue at http://www.github.com/appium/ruby_lib if so."
281
- Appium::Logger.warn msg
282
- end
283
-
284
268
  # @!method accessiblity_id_find
285
269
  # find_element/s with their accessibility_id
286
270
  #
@@ -24,7 +24,6 @@ require_relative 'ios/element/text'
24
24
  require_relative 'ios/mobile_methods'
25
25
 
26
26
  # android
27
- require_relative 'android/dynamic'
28
27
  require_relative 'android/helper'
29
28
  require_relative 'android/patch'
30
29
  require_relative 'android/element/alert'
@@ -210,7 +209,17 @@ module Appium
210
209
  # made available via the driver_attributes method
211
210
 
212
211
  # The amount to sleep in seconds before every webdriver http call.
213
- attr_accessor :global_webdriver_http_sleep
212
+ attr_accessor :global_webdriver_http_sleep,
213
+ :caps,
214
+ :custom_url,
215
+ :export_session,
216
+ :default_wait,
217
+ :last_waits,
218
+ :sauce_username,
219
+ :sauce_access_key,
220
+ :appium_port,
221
+ :appium_device,
222
+ :appium_debug
214
223
 
215
224
  # Creates a new driver
216
225
  #
@@ -251,7 +260,7 @@ module Appium
251
260
  @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?)
252
261
  @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY']
253
262
  @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?)
254
- @port = appium_lib_opts.fetch :port, 4723
263
+ @appium_port = appium_lib_opts.fetch :port, 4723
255
264
 
256
265
  # Path to the .apk, .app or .app.zip.
257
266
  # The path can be local or remote for Sauce.
@@ -260,13 +269,14 @@ module Appium
260
269
  end
261
270
 
262
271
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
263
- @device = @caps[:platformName]
264
- @device = @device.is_a?(Symbol) ? @device : @device.downcase.strip.intern if @device
265
- raise "platformName must be set. Not found in options: #{opts}" unless @device
266
- raise 'platformName must be Android or iOS' unless [:android, :ios].include?(@device)
272
+ @appium_device = @caps[:platformName]
273
+ @appium_device = @appium_device.is_a?(Symbol) ? @appium_device : @appium_device.downcase.strip.intern if @appium_device
274
+ raise "platformName must be set. Not found in options: #{opts}" unless @appium_device
275
+ raise 'platformName must be Android or iOS' unless [:android, :ios].include?(@appium_device)
267
276
 
268
277
  # load common methods
269
278
  extend Appium::Common
279
+ extend Appium::Device
270
280
  if device_is_android?
271
281
  # load Android specific methods
272
282
  extend Appium::Android
@@ -280,12 +290,12 @@ module Appium
280
290
 
281
291
  # enable debug patch
282
292
  # !!'constant' == true
283
- @debug = appium_lib_opts.fetch :debug, !!defined?(Pry)
293
+ @appium_debug = appium_lib_opts.fetch :debug, !!defined?(Pry)
284
294
 
285
- if @debug
295
+ if @appium_debug
286
296
  ap opts unless opts.empty?
287
- puts "Debug is: #{@debug}"
288
- puts "Device is: #{@device}"
297
+ puts "Debug is: #{@appium_debug}"
298
+ puts "Device is: #{@appium_device}"
289
299
  patch_webdriver_bridge
290
300
  end
291
301
 
@@ -297,9 +307,6 @@ module Appium
297
307
  # Subsequent drivers do not trigger promotion.
298
308
  unless @@loaded
299
309
  @@loaded = true
300
- # load device methods exactly once
301
- extend Appium::Device
302
-
303
310
  # Promote only on Minitest::Spec (minitest 5) by default
304
311
  Appium.promote_appium_methods ::Minitest::Spec
305
312
  end
@@ -316,9 +323,9 @@ module Appium
316
323
  last_waits: @last_waits,
317
324
  sauce_username: @sauce_username,
318
325
  sauce_access_key: @sauce_access_key,
319
- port: @port,
320
- device: @device,
321
- debug: @debug,
326
+ port: @appium_port,
327
+ device: @appium_device,
328
+ debug: @appium_debug,
322
329
  }
323
330
 
324
331
  # Return duplicates so attributes are immutable
@@ -329,7 +336,7 @@ module Appium
329
336
  end
330
337
 
331
338
  def device_is_android?
332
- @device == :android
339
+ @appium_device == :android
333
340
  end
334
341
 
335
342
  # Returns the server's version info
@@ -352,6 +359,9 @@ module Appium
352
359
  # @return [String] APP_PATH as an absolute path
353
360
  def self.absolute_app_path app_path
354
361
  raise 'APP_PATH not set!' if app_path.nil? || app_path.empty?
362
+ # may be absolute path to file on remote server.
363
+ # if the file is on the remote server then we can't check if it exists
364
+ return app_path if @custom_url
355
365
  # Sauce storage API. http://saucelabs.com/docs/rest#storage
356
366
  return app_path if app_path.start_with? 'sauce-storage:'
357
367
  return app_path if app_path.match(/^http/) # public URL for Sauce
@@ -379,7 +389,7 @@ module Appium
379
389
  if !@sauce_username.nil? && !@sauce_access_key.nil?
380
390
  "http://#{@sauce_username}:#{@sauce_access_key}@ondemand.saucelabs.com:80/wd/hub"
381
391
  else
382
- "http://127.0.0.1:#{@port}/wd/hub"
392
+ "http://127.0.0.1:#{@appium_port}/wd/hub"
383
393
  end
384
394
  end
385
395
 
@@ -132,7 +132,7 @@ module Appium
132
132
  end
133
133
  # current_context may be nil which breaks start_with
134
134
  if current_context && current_context.start_with?('WEBVIEW')
135
- s = get_source
135
+ s = get_source
136
136
  parser = @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Common::HTMLElements.new)
137
137
  parser.document.reset
138
138
  parser.document.filter = class_name
@@ -354,5 +354,54 @@ module Appium
354
354
  def xpaths_visible_exact element, value
355
355
  xpaths string_visible_exact element, value
356
356
  end
357
+
358
+ # @private
359
+ # If there's no keyboard, then do nothing.
360
+ # If there's no close key, fallback to window tap.
361
+ # If close key is present then tap it.
362
+ # @param close_key [String] close key to tap. Default value is 'Done'
363
+ # @return [void]
364
+ def hide_ios_keyboard close_key='Done'
365
+ =begin
366
+ Find the top left corner of the keyboard and move up 10 pixels (origin.y - 10)
367
+ now swipe down until the end of the window - 10 pixels.
368
+ -10 to ensure we're not going outside the window bounds.
369
+
370
+ Swiping inside the keyboard will not dismiss it.
371
+
372
+ Don't use window.tap. See https://github.com/appium/appium-uiauto/issues/28
373
+ =end
374
+ dismiss_keyboard = (<<-JS).strip
375
+ if (!au.mainApp().keyboard().isNil()) {
376
+ var key = au.mainApp().keyboard().buttons()['#{close_key}']
377
+ if (key.isNil()) {
378
+ var startY = au.mainApp().keyboard().rect().origin.y - 10;
379
+ var endY = au.mainWindow().rect().size.height - 10;
380
+ au.flickApp(0, startY, 0, endY);
381
+ } else {
382
+ key.tap();
383
+ }
384
+ au.delay(1000);
385
+ }
386
+ JS
387
+
388
+ ignore do
389
+ # wait 5 seconds for a wild keyboard to appear. if the textfield is disabled
390
+ # then setValue will work, however the keyboard will never display
391
+ # because users are normally not allowed to type into it.
392
+ wait_true(5) do
393
+ execute_script '!au.mainApp().keyboard().isNil()'
394
+ end
395
+
396
+ # dismiss keyboard
397
+ execute_script dismiss_keyboard
398
+ end
399
+
400
+ # wait 5 seconds for keyboard to go away.
401
+ # if the keyboard isn't dismissed then wait_true will error.
402
+ wait_true(5) do
403
+ execute_script 'au.mainApp().keyboard().isNil()'
404
+ end
405
+ end
357
406
  end # module Ios
358
407
  end # module Appium