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
@@ -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