appium_lib_core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +20 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +202 -0
  8. data/README.md +5 -0
  9. data/Rakefile +62 -0
  10. data/Thorfile +7 -0
  11. data/appium_lib_core.gemspec +35 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/appium_lib_core.rb +41 -0
  15. data/lib/appium_lib_core/android.rb +5 -0
  16. data/lib/appium_lib_core/android/device.rb +174 -0
  17. data/lib/appium_lib_core/android/espresso/bridge.rb +16 -0
  18. data/lib/appium_lib_core/android/search_context.rb +18 -0
  19. data/lib/appium_lib_core/android/touch.rb +17 -0
  20. data/lib/appium_lib_core/android/uiautomator1/bridge.rb +16 -0
  21. data/lib/appium_lib_core/android/uiautomator2/bridge.rb +16 -0
  22. data/lib/appium_lib_core/android_espresso.rb +5 -0
  23. data/lib/appium_lib_core/android_uiautomator2.rb +5 -0
  24. data/lib/appium_lib_core/common.rb +6 -0
  25. data/lib/appium_lib_core/common/base.rb +8 -0
  26. data/lib/appium_lib_core/common/base/bridge.rb +47 -0
  27. data/lib/appium_lib_core/common/base/capabilities.rb +16 -0
  28. data/lib/appium_lib_core/common/base/command.rb +10 -0
  29. data/lib/appium_lib_core/common/base/driver.rb +51 -0
  30. data/lib/appium_lib_core/common/base/http_default.rb +13 -0
  31. data/lib/appium_lib_core/common/base/search_context.rb +89 -0
  32. data/lib/appium_lib_core/common/base/wait.rb +56 -0
  33. data/lib/appium_lib_core/common/command.rb +77 -0
  34. data/lib/appium_lib_core/common/device.rb +567 -0
  35. data/lib/appium_lib_core/common/error.rb +18 -0
  36. data/lib/appium_lib_core/common/log.rb +30 -0
  37. data/lib/appium_lib_core/common/logger.rb +30 -0
  38. data/lib/appium_lib_core/device/multi_touch.rb +48 -0
  39. data/lib/appium_lib_core/device/touch_actions.rb +191 -0
  40. data/lib/appium_lib_core/driver.rb +459 -0
  41. data/lib/appium_lib_core/ios.rb +7 -0
  42. data/lib/appium_lib_core/ios/device.rb +54 -0
  43. data/lib/appium_lib_core/ios/search_context.rb +27 -0
  44. data/lib/appium_lib_core/ios/touch.rb +17 -0
  45. data/lib/appium_lib_core/ios/uiautomation/bridge.rb +18 -0
  46. data/lib/appium_lib_core/ios/uiautomation/patch.rb +20 -0
  47. data/lib/appium_lib_core/ios/xcuitest/bridge.rb +18 -0
  48. data/lib/appium_lib_core/ios/xcuitest/device.rb +66 -0
  49. data/lib/appium_lib_core/ios/xcuitest/search_context.rb +40 -0
  50. data/lib/appium_lib_core/ios_xcuitest.rb +8 -0
  51. data/lib/appium_lib_core/patch.rb +78 -0
  52. data/lib/appium_lib_core/version.rb +6 -0
  53. data/release_notes.md +0 -0
  54. metadata +262 -0
@@ -0,0 +1,89 @@
1
+ module Appium
2
+ module Core
3
+ class Base
4
+ module SearchContext
5
+ # referenced: ::Selenium::WebDriver::SearchContext
6
+
7
+ FINDERS = ::Selenium::WebDriver::SearchContext::FINDERS.merge(accessibility_id: 'accessibility id')
8
+
9
+ def self.add_finders(finders)
10
+ FINDERS.merge!(finders)
11
+ end
12
+
13
+ #
14
+ # Find the first element matching the given arguments
15
+ #
16
+ # @overload find_element(how, what)
17
+ # @param [Symbol, String] how The method to find the element by
18
+ # @param [String] what The locator to use
19
+ # @overload find_element(opts)
20
+ # @param [Hash] opts Find options
21
+ # @option opts [Symbol] :how Key named after the method to find the element by, containing the locator
22
+ # @return [Element]
23
+ #
24
+ # @raise [Error::NoSuchElementError] if the element doesn't exist
25
+ #
26
+ # @example Find element with accessibility id
27
+ # find_elements :accessibility_id, 'Animation'
28
+ # find_elements :accessibility_id, 'Animation'
29
+ #
30
+ def find_element(*args)
31
+ how, what = extract_args(args)
32
+ by = _set_by_from_finders(how)
33
+ begin
34
+ bridge.find_element_by by, what.to_s, ref
35
+ rescue Selenium::WebDriver::Error::TimeOutError
36
+ raise Selenium::WebDriver::Error::NoSuchElementError
37
+ end
38
+ end
39
+
40
+ #
41
+ # Find all elements matching the given arguments
42
+ #
43
+ # @see SearchContext#find_element
44
+ #
45
+ def find_elements(*args)
46
+ how, what = extract_args(args)
47
+ by = _set_by_from_finders(how)
48
+ begin
49
+ bridge.find_elements_by by, what.to_s, ref
50
+ rescue Selenium::WebDriver::Error::TimeOutError
51
+ raise Selenium::WebDriver::Error::NoSuchElementError
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def _set_by_from_finders(how)
58
+ finders = FINDERS
59
+ by = finders[how.to_sym]
60
+ raise ArgumentError, "cannot find element by #{how.inspect}" unless by
61
+ by
62
+ end
63
+
64
+ def extract_args(args)
65
+ case args.size
66
+ when 2
67
+ args
68
+ when 1
69
+ arg = args.first
70
+
71
+ unless arg.respond_to?(:shift)
72
+ raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
73
+ end
74
+
75
+ # this will be a single-entry hash, so use #shift over #first or #[]
76
+ arr = arg.dup.shift
77
+ unless arr.size == 2
78
+ raise ArgumentError, "expected #{arr.inspect} to have 2 elements"
79
+ end
80
+
81
+ arr
82
+ else
83
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
84
+ end
85
+ end
86
+ end # module SearchContext
87
+ end # class Base
88
+ end # module Core
89
+ end # module Appium
@@ -0,0 +1,56 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+ module Appium
3
+ module Core
4
+ class Base
5
+ class Wait < ::Selenium::WebDriver::Wait
6
+ require 'timeout' # for wait
7
+
8
+ def initialize(opts = {})
9
+ valid_keys = [:timeout, :interval, :message, :ignore, :return_if_true]
10
+ invalid_keys = []
11
+ opts.keys.each { |key| invalid_keys << key unless valid_keys.include?(key) }
12
+ # [:one, :two] => :one, :two
13
+ unless invalid_keys.empty?
14
+ raise "Invalid keys #{invalid_keys.to_s[1..-2]}. Valid keys are #{valid_keys.to_s[1..-2]}"
15
+ end
16
+
17
+ @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
18
+ @interval = opts.fetch(:interval, DEFAULT_INTERVAL)
19
+ @message = opts[:message]
20
+ @ignored = Array(opts[:ignore] || ::Exception)
21
+ @return_if_true = opts[:return_if_true]
22
+
23
+ super(timeout: @timeout, interval: @interval, message: @message, ignore: @ignored)
24
+ end
25
+
26
+ # Wait code from the selenium Ruby gem
27
+ # https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
28
+ def until
29
+ end_time = Time.now + @timeout
30
+ last_error = nil
31
+
32
+ until Time.now > end_time
33
+ begin
34
+ return yield unless @return_if_true
35
+
36
+ result = yield
37
+ return result if result
38
+ rescue ::Errno::ECONNREFUSED => e
39
+ raise e
40
+ rescue *@ignored => last_error
41
+ # swallowed
42
+ end
43
+
44
+ sleep @interval
45
+ end
46
+
47
+ msg = @message ? @message.dup : "timed out after #{@timeout} seconds"
48
+
49
+ msg << " (#{last_error.message})" if last_error
50
+
51
+ raise Selenium::WebDriver::Error::TimeOutError, msg
52
+ end
53
+ end # module Wait
54
+ end # module Base
55
+ end # module Core
56
+ end # module Appium
@@ -0,0 +1,77 @@
1
+ require_relative 'base/command'
2
+
3
+ module Appium
4
+ module Core
5
+ # ref: https://github.com/appium/appium-base-driver/blob/master/lib/mjsonwp/routes.js
6
+ module Commands
7
+ COMMAND_NO_ARG = {
8
+ # Common
9
+ shake: [:post, 'session/:session_id/appium/device/shake'.freeze],
10
+ launch_app: [:post, 'session/:session_id/appium/app/launch'.freeze],
11
+ close_app: [:post, 'session/:session_id/appium/app/close'.freeze],
12
+ reset: [:post, 'session/:session_id/appium/app/reset'.freeze],
13
+ device_locked?: [:post, 'session/:session_id/appium/device/is_locked'.freeze],
14
+ unlock: [:post, 'session/:session_id/appium/device/unlock'.freeze],
15
+ device_time: [:get, 'session/:session_id/appium/device/system_time'.freeze],
16
+ current_context: [:get, 'session/:session_id/context'.freeze],
17
+
18
+ # Android
19
+ open_notifications: [:post, 'session/:session_id/appium/device/open_notifications'.freeze],
20
+ toggle_airplane_mode: [:post, 'session/:session_id/appium/device/toggle_airplane_mode'.freeze],
21
+ current_activity: [:get, 'session/:session_id/appium/device/current_activity'.freeze],
22
+ current_package: [:get, 'session/:session_id/appium/device/current_package'.freeze],
23
+ get_system_bars: [:get, 'session/:session_id/appium/device/system_bars'.freeze],
24
+ get_display_density: [:get, 'session/:session_id/appium/device/display_density'.freeze],
25
+ is_keyboard_shown: [:get, 'session/:session_id/appium/device/is_keyboard_shown'.freeze],
26
+ get_network_connection: [:get, 'session/:session_id/network_connection'.freeze],
27
+ get_performance_data_types: [:post, 'session/:session_id/appium/performanceData/types'.freeze],
28
+ stop_recording_screen: [:post, 'session/:session_id/appium/stop_recording_screen'.freeze]
29
+ # iOS
30
+ }.freeze
31
+
32
+ # Some commands differ for each driver.
33
+ COMMAND = {
34
+ # common
35
+ available_contexts: [:get, 'session/:session_id/contexts'.freeze],
36
+ set_context: [:post, 'session/:session_id/context'.freeze],
37
+ app_strings: [:post, 'session/:session_id/appium/app/strings'.freeze],
38
+ lock: [:post, 'session/:session_id/appium/device/lock'.freeze],
39
+ install_app: [:post, 'session/:session_id/appium/device/install_app'.freeze],
40
+ remove_app: [:post, 'session/:session_id/appium/device/remove_app'.freeze],
41
+ app_installed?: [:post, 'session/:session_id/appium/device/app_installed'.freeze],
42
+ background_app: [:post, 'session/:session_id/appium/app/background'.freeze],
43
+ hide_keyboard: [:post, 'session/:session_id/appium/device/hide_keyboard'.freeze],
44
+ press_keycode: [:post, 'session/:session_id/appium/device/press_keycode'.freeze],
45
+ long_press_keycode: [:post, 'session/:session_id/appium/device/long_press_keycode'.freeze],
46
+ set_immediate_value: [:post, 'session/:session_id/appium/element/:id/value'.freeze],
47
+ replace_value: [:post, 'session/:session_id/appium/element/:id/replace_value'.freeze],
48
+ push_file: [:post, 'session/:session_id/appium/device/push_file'.freeze],
49
+ pull_file: [:post, 'session/:session_id/appium/device/pull_file'.freeze],
50
+ pull_folder: [:post, 'session/:session_id/appium/device/pull_folder'.freeze],
51
+ get_settings: [:get, 'session/:session_id/appium/settings'.freeze],
52
+ update_settings: [:post, 'session/:session_id/appium/settings'.freeze],
53
+ touch_actions: [:post, 'session/:session_id/touch/perform'.freeze],
54
+ multi_touch: [:post, 'session/:session_id/touch/multi/perform'.freeze]
55
+ }.freeze
56
+
57
+ COMMAND_ANDROID = {
58
+ start_activity: [:post, 'session/:session_id/appium/device/start_activity'.freeze],
59
+ end_coverage: [:post, 'session/:session_id/appium/app/end_test_coverage'.freeze],
60
+ set_network_connection: [:post, 'session/:session_id/network_connection'.freeze],
61
+ get_performance_data: [:post, 'session/:session_id/appium/getPerformanceData'.freeze],
62
+ start_recording_screen: [:post, 'session/:session_id/appium/start_recording_screen'.freeze]
63
+ }.freeze
64
+
65
+ COMMAND_IOS = {
66
+ touch_id: [:post, 'session/:session_id/appium/simulator/touch_id'.freeze],
67
+ toggle_touch_id_enrollment: [:post, 'session/:session_id/appium/simulator/toggle_touch_id_enrollment'.freeze]
68
+ }.freeze
69
+
70
+ COMMANDS = {}.merge(COMMAND).merge(COMMAND_ANDROID).merge(COMMAND_IOS)
71
+ .merge(COMMAND_NO_ARG).freeze
72
+
73
+ COMMANDS_EXTEND_OSS = COMMANDS.merge(::Appium::Core::Base::Commands::OSS).freeze
74
+ COMMANDS_EXTEND_W3C = COMMANDS.merge(::Appium::Core::Base::Commands::W3C).freeze
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,567 @@
1
+ require 'base64'
2
+
3
+ module Appium
4
+ module Core
5
+ module Device
6
+ extend Forwardable
7
+
8
+ ####
9
+ ## No argument
10
+ ####
11
+
12
+ # @!method current_activity
13
+ # Get current activity name
14
+ # @return [String] An activity name
15
+ #
16
+ # @example
17
+ #
18
+ # @driver.current_activity # '.ApiDemos'
19
+ #
20
+
21
+ # @!method current_package
22
+ # Get current package name
23
+ # @return [String] A package name
24
+ #
25
+ # @example
26
+ #
27
+ # @driver.current_package # 'com.example.android.apis'
28
+ #
29
+
30
+ # @!method get_system_bars
31
+ # Get system bar's information
32
+ # @return [String]
33
+ #
34
+ # @example
35
+ #
36
+ # @driver.get_system_bars
37
+ #
38
+
39
+ # @!method get_display_density
40
+ # Get connected device's density.
41
+ # @return [Integer] The size of density
42
+ #
43
+ # @example
44
+ #
45
+ # @driver.get_display_density # 320
46
+ #
47
+
48
+ # @!method is_keyboard_shown
49
+ # Get whether keyboard is displayed or not.
50
+ # @return [Bool] Return true if keyboard is shown. Return false if keyboard is hidden.
51
+ #
52
+ # @example
53
+ # @driver.is_keyboard_shown # false
54
+ #
55
+
56
+ # @!method launch_app
57
+ # Start the simulator and application configured with desired capabilities
58
+ #
59
+ # @example
60
+ #
61
+ # @driver.launch_app
62
+ #
63
+
64
+ # @!method reset
65
+ # Reset the device, relaunching the application.
66
+ #
67
+ # @example
68
+ #
69
+ # @driver.reset
70
+ #
71
+
72
+ # @!method shake
73
+ # Cause the device to shake
74
+ #
75
+ # @example
76
+ #
77
+ # @driver.shake
78
+ #
79
+
80
+ # @!method toggle_flight_mode
81
+ # Toggle flight mode on or off
82
+ #
83
+ # @example
84
+ #
85
+ # @driver.toggle_flight_mode
86
+ #
87
+
88
+ # @!method device_locked?
89
+ # Check current device status is weather locked or not
90
+ #
91
+ # @example
92
+ #
93
+ # @driver.device_locked?
94
+ #
95
+
96
+ # @!method get_network_connection
97
+ # Get the device network connection current status
98
+ # See set_network_connection method for return value
99
+
100
+ ####
101
+ ## With arguments
102
+ ####
103
+
104
+ # @!method app_strings(language = nil)
105
+ # Return the hash of all localization strings.
106
+ # @return [Hash]
107
+ #
108
+ # @example
109
+ #
110
+ # @driver.app_strings #=> "TransitionsTitle"=>"Transitions", "WebTitle"=>"Web"
111
+ #
112
+
113
+ # @!method background_app(duration = 0)
114
+ # Backgrounds the app for a set number of seconds.
115
+ # This is a blocking application
116
+ # @param [Integer] duration How many seconds to background the app for.
117
+ # @return [String]
118
+ #
119
+ # @example
120
+ #
121
+ # @driver.background_app
122
+ # @driver.background_app(5)
123
+ # @driver.background_app(-1) #=> the app never come back. https://github.com/appium/appium/issues/7741
124
+ #
125
+
126
+ # @!method hide_keyboard(close_key = nil, strategy = nil)
127
+ # Hide the onscreen keyboard
128
+ # @param [String] close_key The name of the key which closes the keyboard.
129
+ # Defaults to 'Done' for iOS(except for XCUITest).
130
+ # @param [Symbol] strategy The symbol of the strategy which closes the keyboard.
131
+ # XCUITest ignore this argument.
132
+ # Default for iOS is `:pressKey`. Default for Android is `:tapOutside`.
133
+ #
134
+ # @example
135
+ #
136
+ # @driver.hide_keyboard # Close a keyboard with the 'Done' button
137
+ # @driver.hide_keyboard('Finished') # Close a keyboard with the 'Finished' button
138
+ # @driver.hide_keyboard(nil, :tapOutside) # Close a keyboard with tapping out side of keyboard
139
+ #
140
+
141
+ # @!method press_keycode(key, metastate = nil)
142
+ # Press keycode on the device.
143
+ # http://developer.android.com/reference/android/view/KeyEvent.html
144
+ # @param [integer] key The key to press.
145
+ # @param [String] metastate The state the metakeys should be in when pressing the key.
146
+ #
147
+ # @example
148
+ #
149
+ # @driver.press_keycode 82
150
+ #
151
+
152
+ # @!method long_press_keycode(key, metastate = nil)
153
+ # Long press keycode on the device.
154
+ # http://developer.android.com/reference/android/view/KeyEvent.html
155
+ # @param [integer] key The key to long press.
156
+ # @param [String] metastate The state the metakeys should be in when long pressing the key.
157
+ #
158
+ # @example
159
+ #
160
+ # @driver.long_press_keycode 82
161
+ #
162
+
163
+ # @!method push_file(path, filedata)
164
+ # Place a file in a specific location on the device.
165
+ # @param [String] path The absolute path on the device to store data at.
166
+ # @param [String] filedata Raw file data to be sent to the device. Converted to base64 in the method.
167
+ #
168
+ # @example
169
+ #
170
+ # @driver.push_file "/file/to/path", data
171
+ #
172
+
173
+ # @!method pull_file(path)
174
+ # Retrieve a file from the device. This can retrieve an absolute path or
175
+ # a path relative to the installed app (iOS only).
176
+ # @param [String] path Either an absolute path OR, for iOS devices, a path relative to the app, as described.
177
+ #
178
+ # @example
179
+ #
180
+ # @driver.pull_file '/local/data/some/path' #=> Get the file at that path
181
+ # @driver.pull_file 'Shenanigans.app/some/file' #=> Get 'some/file' from the install location of Shenanigans.app
182
+ #
183
+
184
+ # @!method pull_folder(path)
185
+ # Retrieve a folder from the device.
186
+ # @param [String] path absolute path to the folder
187
+ #
188
+ # @example
189
+ #
190
+ # @driver.pull_folder '/data/local/tmp' #=> Get the folder at that path
191
+ #
192
+
193
+ # @!method get_settings
194
+ # Get appium Settings for current test session
195
+ #
196
+ # @example
197
+ #
198
+ # @driver.pull_folder '/data/local/tmp' #=> Get the folder at that path
199
+ #
200
+
201
+ # @!method update_settings(settings)
202
+ # Update Appium Settings for current test session
203
+ # @param [Hash] settings Settings to update, keys are settings, values to value to set each setting to
204
+ #
205
+ # @example
206
+ #
207
+ # @driver.update_settings('allowInvisibleElements': true)
208
+ #
209
+
210
+ # @!method set_immediate_value(element, *value)
211
+ # Set the value to element directly
212
+ #
213
+ # @example
214
+ #
215
+ # set_immediate_value element, 'hello'
216
+ #
217
+
218
+ # @!method replace_value(element, *value)
219
+ # Replace the value to element directly
220
+ #
221
+ # @example
222
+ #
223
+ # replace_value element, 'hello'
224
+ #
225
+
226
+ # @!method ime_activate(ime_name)
227
+ # Android only. Make an engine that is available active.
228
+ # @param [String] ime_name The IME owning the activity [required]
229
+ #
230
+ # @example
231
+ #
232
+ # ime_activate engine: 'com.android.inputmethod.latin/.LatinIME'
233
+ #
234
+
235
+ # @!method ime_available_engines
236
+ # Android only. List all available input engines on the machine.
237
+ #
238
+ # @example
239
+ #
240
+ # ime_available_engines #=> Get the list of IME installed in the target device
241
+ #
242
+
243
+ # @!method ime_active_engine
244
+ # Android only. Get the name of the active IME engine.
245
+ #
246
+ # @example
247
+ #
248
+ # ime_active_engine #=> Get the current active IME such as 'com.android.inputmethod.latin/.LatinIME'
249
+ #
250
+
251
+ # @!method ime_activated
252
+ # Android only. Indicates whether IME input is active at the moment (not if it is available).
253
+ #
254
+ # @example
255
+ #
256
+ # ime_activated #=> True if IME is activated
257
+ #
258
+
259
+ # @!method ime_deactivate
260
+ # Android only. De-activates the currently-active IME engine.
261
+ #
262
+ # @example
263
+ #
264
+ # ime_deactivate #=> Deactivate current IME engine
265
+ #
266
+
267
+ ####
268
+ ## class << self
269
+ ####
270
+
271
+ class << self
272
+ def extended(_mod)
273
+ extend_webdriver_with_forwardable
274
+
275
+ ::Appium::Core::Commands::COMMAND_NO_ARG.each_key do |method|
276
+ add_endpoint_method method
277
+ end
278
+
279
+ # call http://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/DriverExtensions/HasRemoteStatus#remote_status-instance_method
280
+ add_endpoint_method(:remote_status) do
281
+ def remote_status
282
+ execute(:status, {}) || []
283
+ end
284
+ end
285
+
286
+ # TODO: Don't define selenium-side methods. We pick up from them.
287
+ # ::Appium::Core::Base::Commands::OSS.each_key do |method|
288
+ # add_endpoint_method method
289
+ # end
290
+
291
+ add_endpoint_method(:available_contexts) do
292
+ def available_contexts
293
+ # return empty array instead of nil on failure
294
+ execute(:available_contexts, {}) || []
295
+ end
296
+ end
297
+
298
+ add_endpoint_method(:app_strings) do
299
+ def app_strings(language = nil)
300
+ opts = language ? { language: language } : {}
301
+ execute :app_strings, {}, opts
302
+ end
303
+ end
304
+
305
+ add_endpoint_method(:lock) do
306
+ def lock(duration)
307
+ execute :lock, {}, seconds: duration
308
+ end
309
+ end
310
+
311
+ add_endpoint_method(:install_app) do
312
+ def install_app(path)
313
+ execute :install_app, {}, appPath: path
314
+ end
315
+ end
316
+
317
+ add_endpoint_method(:remove_app) do
318
+ def remove_app(id)
319
+ execute :remove_app, {}, appId: id
320
+ end
321
+ end
322
+
323
+ add_endpoint_method(:app_installed?) do
324
+ def app_installed?(app_id)
325
+ execute :app_installed?, {}, bundleId: app_id
326
+ end
327
+ end
328
+
329
+ add_endpoint_method(:background_app) do
330
+ def background_app(duration = 0)
331
+ execute :background_app, {}, seconds: duration
332
+ end
333
+ end
334
+
335
+ add_endpoint_method(:set_context) do
336
+ def set_context(context = null)
337
+ execute :set_context, {}, name: context
338
+ end
339
+ end
340
+
341
+ add_endpoint_method(:hide_keyboard) do
342
+ def hide_keyboard(close_key = nil, strategy = nil)
343
+ option = {}
344
+
345
+ option[:key] = close_key || 'Done' # default to Done key.
346
+ option[:strategy] = strategy || :pressKey # default to pressKey
347
+
348
+ execute :hide_keyboard, {}, option
349
+ end
350
+ end
351
+
352
+ add_endpoint_method(:press_keycode) do
353
+ def press_keycode(key, metastate = nil)
354
+ args = { keycode: key }
355
+ args[:metastate] = metastate if metastate
356
+ execute :press_keycode, {}, args
357
+ end
358
+ end
359
+
360
+ add_endpoint_method(:long_press_keycode) do
361
+ def long_press_keycode(key, metastate = nil)
362
+ args = { keycode: key }
363
+ args[:metastate] = metastate if metastate
364
+ execute :long_press_keycode, {}, args
365
+ end
366
+ end
367
+
368
+ add_endpoint_method(:set_immediate_value) do
369
+ def set_immediate_value(element, *value)
370
+ keys = ::Selenium::WebDriver::Keys.encode(value)
371
+ execute :set_immediate_value, { id: element.ref }, value: Array(keys)
372
+ end
373
+ end
374
+
375
+ add_endpoint_method(:replace_value) do
376
+ def replace_value(element, *value)
377
+ keys = ::Selenium::WebDriver::Keys.encode(value)
378
+ execute :replace_value, { id: element.ref }, value: Array(keys)
379
+ end
380
+ end
381
+
382
+ add_endpoint_method(:push_file) do
383
+ def push_file(path, filedata)
384
+ encoded_data = Base64.encode64 filedata
385
+ execute :push_file, {}, path: path, data: encoded_data
386
+ end
387
+ end
388
+
389
+ add_endpoint_method(:pull_file) do
390
+ def pull_file(path)
391
+ data = execute :pull_file, {}, path: path
392
+ Base64.decode64 data
393
+ end
394
+ end
395
+
396
+ add_endpoint_method(:pull_folder) do
397
+ def pull_folder(path)
398
+ data = execute :pull_folder, {}, path: path
399
+ Base64.decode64 data
400
+ end
401
+ end
402
+
403
+ add_endpoint_method(:get_settings) do
404
+ def get_settings
405
+ execute :get_settings, {}
406
+ end
407
+ end
408
+
409
+ add_endpoint_method(:update_settings) do
410
+ def update_settings(settings)
411
+ execute :update_settings, {}, settings: settings
412
+ end
413
+ end
414
+
415
+ add_touch_actions
416
+ add_ime_actions
417
+ end
418
+
419
+ # def extended
420
+
421
+ # @private
422
+ def add_endpoint_method(method)
423
+ block_given? ? create_bridge_command(method, &Proc.new) : create_bridge_command(method)
424
+
425
+ delegate_driver_method method
426
+ delegate_from_appium_driver method
427
+ end
428
+
429
+ # @private CoreBridge
430
+ def extend_webdriver_with_forwardable
431
+ return if ::Appium::Core::Base::Driver.is_a? Forwardable
432
+ ::Appium::Core::Base::Driver.class_eval do
433
+ extend Forwardable
434
+ end
435
+ end
436
+
437
+ # @private
438
+ def delegate_driver_method(method)
439
+ return if ::Appium::Core::Base::Driver.method_defined? method
440
+ ::Appium::Core::Base::Driver.class_eval { def_delegator :@bridge, method }
441
+ end
442
+
443
+ # @private
444
+ def delegate_from_appium_driver(method, delegation_target = :driver)
445
+ def_delegator delegation_target, method
446
+ end
447
+
448
+ # @private
449
+ def create_bridge_command(method)
450
+ ::Appium::Core::Base::CoreBridgeOSS.class_eval do
451
+ block_given? ? class_eval(&Proc.new) : define_method(method) { execute method }
452
+ end
453
+ ::Appium::Core::Base::CoreBridgeW3C.class_eval do
454
+ block_given? ? class_eval(&Proc.new) : define_method(method) { execute method }
455
+ end
456
+ end
457
+
458
+ def add_touch_actions
459
+ add_endpoint_method(:touch_actions) do
460
+ def touch_actions(actions)
461
+ actions = { actions: [actions].flatten }
462
+ execute :touch_actions, {}, actions
463
+ end
464
+ end
465
+
466
+ add_endpoint_method(:multi_touch) do
467
+ def multi_touch(actions)
468
+ execute :multi_touch, {}, actions: actions
469
+ end
470
+ end
471
+ end
472
+
473
+ def add_ime_actions
474
+ add_endpoint_method(:ime_activate) do
475
+ def ime_activate(ime_name)
476
+ # from Selenium::WebDriver::Remote::OSS
477
+ execute :ime_activate_engine, {}, engine: ime_name
478
+ end
479
+ end
480
+
481
+ add_endpoint_method(:ime_available_engines) do
482
+ def ime_available_engines
483
+ execute :ime_get_available_engines
484
+ end
485
+ end
486
+
487
+ add_endpoint_method(:ime_active_engine) do
488
+ # from Selenium::WebDriver::Remote::OSS
489
+ def ime_active_engine
490
+ execute :ime_get_active_engine
491
+ end
492
+ end
493
+
494
+ add_endpoint_method(:ime_activated) do
495
+ # from Selenium::WebDriver::Remote::OSS
496
+ def ime_activated
497
+ execute :ime_is_activated
498
+ end
499
+ end
500
+
501
+ add_endpoint_method(:ime_deactivate) do
502
+ # from Selenium::WebDriver::Remote::OSS
503
+ def ime_deactivate
504
+ execute :ime_deactivate, {}
505
+ end
506
+ end
507
+ end
508
+ end # class << self
509
+
510
+ # @!method set_context(context)
511
+ # Change the context to the given context.
512
+ # @param [String] context The context to change to
513
+ #
514
+ # @example
515
+ #
516
+ # @driver.set_context "NATIVE_APP"
517
+ #
518
+
519
+ # @!method current_context
520
+ # @return [String] The context currently being used.
521
+ #
522
+ # @example
523
+ #
524
+ # @driver.current_context
525
+ #
526
+
527
+ # @!method available_contexts
528
+ # @return [Array<String>] All usable contexts, as an array of strings.
529
+ #
530
+ # @example
531
+ #
532
+ # @driver.available_contexts
533
+ #
534
+
535
+ # Perform a block within the given context, then switch back to the starting context.
536
+ # @param [String] context The context to switch to for the duration of the block.
537
+ #
538
+ # @example
539
+ #
540
+ # result = @driver.within_context('NATIVE_APP') do
541
+ # @driver.find_element :tag, "button"
542
+ # end # The result of `find_element :tag, "button"`
543
+ #
544
+ def within_context(context)
545
+ existing_context = current_context
546
+ set_context context
547
+ if block_given?
548
+ result = yield
549
+ set_context existing_context
550
+ result
551
+ else
552
+ set_context existing_context
553
+ end
554
+ end
555
+
556
+ # Change to the default context. This is equivalent to `set_context nil`.
557
+ #
558
+ # @example
559
+ #
560
+ # @driver.switch_to_default_context
561
+ #
562
+ def switch_to_default_context
563
+ set_context nil
564
+ end
565
+ end # module Device
566
+ end # module Core
567
+ end # module Appium