appium_lib 9.6.1 → 9.7.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/CHANGELOG.md +43 -0
  4. data/Rakefile +1 -1
  5. data/appium_lib.gemspec +1 -1
  6. data/docs/android_docs.md +440 -1295
  7. data/docs/docs.md +10 -103
  8. data/docs/index_paths.md +2 -0
  9. data/docs/ios_docs.md +725 -1674
  10. data/docs/migration.md +17 -0
  11. data/lib/appium_lib.rb +1 -2
  12. data/lib/appium_lib/android/android.rb +20 -0
  13. data/lib/appium_lib/android/{helper.rb → common/helper.rb} +1 -1
  14. data/lib/appium_lib/android/uiautomator2.rb +5 -4
  15. data/lib/appium_lib/android/uiautomator2/bridge.rb +16 -0
  16. data/lib/appium_lib/appium.rb +201 -0
  17. data/lib/appium_lib/common/helper.rb +18 -20
  18. data/lib/appium_lib/common/log.rb +24 -0
  19. data/lib/appium_lib/common/multi_touch.rb +89 -0
  20. data/lib/appium_lib/common/touch_actions.rb +48 -0
  21. data/lib/appium_lib/common/wait.rb +10 -49
  22. data/lib/appium_lib/core/android.rb +4 -0
  23. data/lib/appium_lib/core/android/device.rb +142 -0
  24. data/lib/appium_lib/core/android/search_context.rb +17 -0
  25. data/lib/appium_lib/core/android/uiautomator1/bridge.rb +16 -0
  26. data/lib/appium_lib/core/android/uiautomator2/bridge.rb +16 -0
  27. data/lib/appium_lib/core/android_uiautomator2.rb +4 -0
  28. data/lib/appium_lib/core/common.rb +6 -0
  29. data/lib/appium_lib/core/common/base.rb +8 -0
  30. data/lib/appium_lib/core/common/base/bridge.rb +47 -0
  31. data/lib/appium_lib/core/common/base/capabilities.rb +16 -0
  32. data/lib/appium_lib/core/common/base/command.rb +10 -0
  33. data/lib/appium_lib/core/common/base/driver.rb +40 -0
  34. data/lib/appium_lib/core/common/base/http_default.rb +12 -0
  35. data/lib/appium_lib/core/common/base/search_context.rb +89 -0
  36. data/lib/appium_lib/core/common/base/wait.rb +56 -0
  37. data/lib/appium_lib/{common → core/common}/command.rb +20 -16
  38. data/lib/appium_lib/core/common/device.rb +470 -0
  39. data/lib/appium_lib/core/common/error.rb +13 -0
  40. data/lib/appium_lib/core/common/log.rb +30 -0
  41. data/lib/appium_lib/{logger.rb → core/common/logger.rb} +2 -0
  42. data/lib/appium_lib/core/core.rb +38 -0
  43. data/lib/appium_lib/core/device/multi_touch.rb +213 -0
  44. data/lib/appium_lib/core/device/touch_actions.rb +206 -0
  45. data/lib/appium_lib/core/driver.rb +274 -0
  46. data/lib/appium_lib/core/ios.rb +6 -0
  47. data/lib/appium_lib/core/ios/device.rb +44 -0
  48. data/lib/appium_lib/core/ios/search_context.rb +27 -0
  49. data/lib/appium_lib/core/ios/uiautomation/bridge.rb +17 -0
  50. data/lib/appium_lib/core/ios/uiautomation/patch.rb +20 -0
  51. data/lib/appium_lib/core/ios/xcuitest/bridge.rb +18 -0
  52. data/lib/appium_lib/{ios → core/ios}/xcuitest/device.rb +5 -5
  53. data/lib/appium_lib/{ios → core/ios}/xcuitest/search_context.rb +13 -9
  54. data/lib/appium_lib/core/ios_xcuitest.rb +7 -0
  55. data/lib/appium_lib/core/patch.rb +56 -0
  56. data/lib/appium_lib/driver.rb +174 -446
  57. data/lib/appium_lib/ios/{errors.rb → common/errors.rb} +0 -0
  58. data/lib/appium_lib/ios/{helper.rb → common/helper.rb} +9 -110
  59. data/lib/appium_lib/ios/ios.rb +20 -0
  60. data/lib/appium_lib/ios/xcuitest.rb +1 -3
  61. data/lib/appium_lib/ios/xcuitest/bridge.rb +19 -0
  62. data/lib/appium_lib/ios/xcuitest/command.rb +4 -1
  63. data/lib/appium_lib/ios/xcuitest/{gestures.rb → command/gestures.rb} +1 -1
  64. data/lib/appium_lib/ios/xcuitest/element.rb +1 -18
  65. data/lib/appium_lib/ios/xcuitest/helper.rb +0 -6
  66. data/lib/appium_lib/sauce_labs.rb +29 -0
  67. data/lib/appium_lib/version.rb +5 -0
  68. data/release_notes.md +8 -0
  69. metadata +50 -25
  70. data/lib/appium_lib/android/client_xpath.rb +0 -51
  71. data/lib/appium_lib/android/device.rb +0 -39
  72. data/lib/appium_lib/android/mobile_methods.rb +0 -15
  73. data/lib/appium_lib/android/patch.rb +0 -16
  74. data/lib/appium_lib/capabilities.rb +0 -13
  75. data/lib/appium_lib/common/element/window.rb +0 -10
  76. data/lib/appium_lib/common/error.rb +0 -8
  77. data/lib/appium_lib/common/patch.rb +0 -190
  78. data/lib/appium_lib/common/search_context.rb +0 -10
  79. data/lib/appium_lib/common/version.rb +0 -5
  80. data/lib/appium_lib/device/device.rb +0 -611
  81. data/lib/appium_lib/device/multi_touch.rb +0 -225
  82. data/lib/appium_lib/device/touch_actions.rb +0 -230
  83. data/lib/appium_lib/ios/mobile_methods.rb +0 -25
  84. data/lib/appium_lib/ios/patch.rb +0 -22
@@ -0,0 +1,12 @@
1
+ module Appium
2
+ module Core
3
+ class Base
4
+ module Http
5
+ class Default < Selenium::WebDriver::Remote::Http::Default
6
+ # TODO: when divide the core to the other gem, then update here. appium/ruby_lib_core/#{version}
7
+ DEFAULT_HEADERS = { 'Accept' => CONTENT_TYPE, 'User-Agent' => "appium/ruby_lib/#{::Appium::VERSION}" }.freeze
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
+ module Appium
2
+ module Core
3
+ class Base
4
+ class Wait < ::Selenium::WebDriver::Wait
5
+ require 'timeout' # for wait
6
+
7
+ def initialize(opts = {})
8
+ valid_keys = [:timeout, :interval, :message, :ignore, :return_if_true]
9
+ invalid_keys = []
10
+ opts.keys.each { |key| invalid_keys << key unless valid_keys.include?(key) }
11
+ # [:one, :two] => :one, :two
12
+ unless invalid_keys.empty?
13
+ raise "Invalid keys #{invalid_keys.to_s[1..-2]}. Valid keys are #{valid_keys.to_s[1..-2]}"
14
+ end
15
+
16
+ @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
17
+ @interval = opts.fetch(:interval, DEFAULT_INTERVAL)
18
+ @message = opts[:message]
19
+ @ignored = Array(opts[:ignore] || ::Exception)
20
+ @return_if_true = opts[:return_if_true]
21
+
22
+ super(timeout: @timeout, interval: @interval, message: @message, ignore: @ignored)
23
+ end
24
+
25
+ # Wait code from the selenium Ruby gem
26
+ # https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
27
+ # @override
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 # rubocop:disable Lint/HandleExceptions
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
@@ -1,13 +1,18 @@
1
+ require_relative 'base/command'
2
+
1
3
  module Appium
2
- class Driver
4
+ module Core
5
+ # ref: https://github.com/appium/appium-base-driver/blob/master/lib/mjsonwp/routes.js
3
6
  module Commands
4
7
  COMMAND_NO_ARG = {
5
- # common
8
+ # Common
6
9
  shake: [:post, 'session/:session_id/appium/device/shake'.freeze],
7
10
  launch_app: [:post, 'session/:session_id/appium/app/launch'.freeze],
8
11
  close_app: [:post, 'session/:session_id/appium/app/close'.freeze],
9
12
  reset: [:post, 'session/:session_id/appium/app/reset'.freeze],
10
13
  device_locked?: [:post, 'session/:session_id/appium/device/is_locked'.freeze],
14
+ device_time: [:get, 'session/:session_id/appium/device/system_time'.freeze],
15
+ current_context: [:get, 'session/:session_id/context'.freeze],
11
16
 
12
17
  # Android
13
18
  open_notifications: [:post, 'session/:session_id/appium/device/open_notifications'.freeze],
@@ -21,10 +26,9 @@ module Appium
21
26
  get_performance_data_types: [:post, 'session/:session_id/appium/performanceData/types'.freeze],
22
27
 
23
28
  # iOS
24
- device_time: [:get, 'session/:session_id/appium/device/system_time'.freeze],
25
- current_context: [:get, 'session/:session_id/context'.freeze]
26
29
  }.freeze
27
30
 
31
+ # Some commands differ for each driver.
28
32
  COMMAND = {
29
33
  # common
30
34
  available_contexts: [:get, 'session/:session_id/contexts'.freeze],
@@ -45,26 +49,26 @@ module Appium
45
49
  get_settings: [:get, 'session/:session_id/appium/settings'.freeze],
46
50
  update_settings: [:post, 'session/:session_id/appium/settings'.freeze],
47
51
  touch_actions: [:post, 'session/:session_id/touch/perform'.freeze],
48
- multi_touch: [:post, 'session/:session_id/touch/multi/perform'.freeze],
52
+ multi_touch: [:post, 'session/:session_id/touch/multi/perform'.freeze]
53
+ }.freeze
49
54
 
50
- # Android
55
+ COMMAND_ANDROID = {
51
56
  start_activity: [:post, 'session/:session_id/appium/device/start_activity'.freeze],
52
57
  end_coverage: [:post, 'session/:session_id/appium/app/end_test_coverage'.freeze],
53
58
  set_network_connection: [:post, 'session/:session_id/network_connection'.freeze],
54
- get_performance_data: [:post, 'session/:session_id/appium/getPerformanceData'.freeze],
55
- get_ime_available_engines: [:get, 'session/:session_id/ime/available_engines'.freeze],
56
- get_ime_active_engine: [:get, 'session/:session_id/ime/active_engine'.freeze],
57
- get_ime_activated: [:get, 'session/:session_id/ime/activated'.freeze],
58
- ime_deactivate: [:post, 'session/:session_id/ime/deactivate'.freeze],
59
- ime_activate: [:post, 'session/:session_id/ime/activate'.freeze],
59
+ get_performance_data: [:post, 'session/:session_id/appium/getPerformanceData'.freeze]
60
+ }.freeze
60
61
 
61
- # iOS
62
+ COMMAND_IOS = {
62
63
  touch_id: [:post, 'session/:session_id/appium/simulator/touch_id'.freeze],
63
64
  toggle_touch_id_enrollment: [:post, 'session/:session_id/appium/simulator/toggle_touch_id_enrollment'.freeze]
64
- }.merge(COMMAND_NO_ARG).freeze
65
+ }.freeze
66
+
67
+ COMMANDS = {}.merge(COMMAND).merge(COMMAND_ANDROID).merge(COMMAND_IOS)
68
+ .merge(COMMAND_NO_ARG).freeze
65
69
 
66
- COMMANDS_EXTEND_OSS = COMMAND.merge(::Selenium::WebDriver::Remote::OSS::Bridge::COMMANDS).freeze
67
- COMMANDS_EXTEND_W3C = COMMAND.merge(::Selenium::WebDriver::Remote::W3C::Bridge::COMMANDS).freeze
70
+ COMMANDS_EXTEND_OSS = COMMANDS.merge(::Appium::Core::Base::Commands::OSS).freeze
71
+ COMMANDS_EXTEND_W3C = COMMANDS.merge(::Appium::Core::Base::Commands::W3C).freeze
68
72
  end
69
73
  end
70
74
  end
@@ -0,0 +1,470 @@
1
+ require 'base64'
2
+
3
+ module Appium
4
+ module Core
5
+ module Device
6
+ extend Forwardable
7
+
8
+ # @!method app_strings
9
+ # Return the hash of all localization strings.
10
+ # ```ruby
11
+ # app_strings #=> "TransitionsTitle"=>"Transitions", "WebTitle"=>"Web"
12
+ # ```
13
+
14
+ # @!method background_app
15
+ # Backgrounds the app for a set number of seconds.
16
+ # This is a blocking application
17
+ # @param [Integer] seconds How many seconds to background the app for.
18
+ #
19
+ # ```ruby
20
+ # background_app
21
+ # background_app(5)
22
+ # background_app(-1) #=> the app never come back. https://github.com/appium/appium/issues/7741
23
+ # ```
24
+
25
+ # @!method current_activity
26
+ # Get current activity name
27
+ # @return [String] An activity name
28
+ #
29
+ # ```ruby
30
+ # current_activity # '.ApiDemos'
31
+ # ```
32
+
33
+ # @!method current_package
34
+ # Get current package name
35
+ # @return [String] A package name
36
+ #
37
+ # ```ruby
38
+ # current_package # 'com.example.android.apis'
39
+ # ```
40
+
41
+ # @!method get_system_bars
42
+ # Get system bar's information
43
+ # @return [String] System bar
44
+ #
45
+ # ```ruby
46
+ # get_system_bars
47
+ # ```
48
+
49
+ # @!method get_display_density
50
+ # Get connected device's density.
51
+ # @return [Integer] The size of density
52
+ #
53
+ # ```ruby
54
+ # get_display_density # 320
55
+ # ```
56
+
57
+ # @!method is_keyboard_shown
58
+ # Get whether keyboard is displayed or not.
59
+ # @return [Bool] Return true if keyboard is shown. Return false if keyboard is hidden.
60
+ #
61
+ # ```ruby
62
+ # is_keyboard_shown # false
63
+ # ```
64
+
65
+ # @!method launch_app
66
+ # Start the simulator and application configured with desired capabilities
67
+
68
+ # @!method reset
69
+ # Reset the device, relaunching the application.
70
+
71
+ # @!method shake
72
+ # Cause the device to shake
73
+
74
+ # @!method toggle_flight_mode
75
+ # Toggle flight mode on or off
76
+
77
+ # @!method device_locked?
78
+
79
+ # @!method hide_keyboard
80
+ # Hide the onscreen keyboard
81
+ # @param [String] close_key The name of the key which closes the keyboard.
82
+ # Defaults to 'Done' for iOS(except for XCUITest).
83
+ # @param [Symbol] strategy The symbol of the strategy which closes the keyboard.
84
+ # XCUITest ignore this argument.
85
+ # Default for iOS is `:pressKey`. Default for Android is `:tapOutside`.
86
+ # ```ruby
87
+ # hide_keyboard # Close a keyboard with the 'Done' button
88
+ # hide_keyboard('Finished') # Close a keyboard with the 'Finished' button
89
+ # hide_keyboard(nil, :tapOutside) # Close a keyboard with tapping out side of keyboard
90
+ # ```
91
+
92
+ # @!method press_keycode
93
+ # Press keycode on the device.
94
+ # http://developer.android.com/reference/android/view/KeyEvent.html
95
+ # @param [integer] key The key to press.
96
+ # @param [String] metastate The state the metakeys should be in when pressing the key.
97
+
98
+ # @!method long_press_keycode
99
+ # Long press keycode on the device.
100
+ # http://developer.android.com/reference/android/view/KeyEvent.html
101
+ # @param [integer] key The key to long press.
102
+ # @param [String] metastate The state the metakeys should be in when long pressing the key.
103
+
104
+ # @!method push_file
105
+ # Place a file in a specific location on the device.
106
+ # @param [String] path The absolute path on the device to store data at.
107
+ # @param [String] data Raw file data to be sent to the device.
108
+
109
+ # @!method pull_file
110
+ # Retrieve a file from the device. This can retrieve an absolute path or
111
+ # a path relative to the installed app (iOS only).
112
+ # @param [String] path Either an absolute path OR, for iOS devices, a path relative to the app, as described.
113
+ #
114
+ # ```ruby
115
+ # pull_file '/local/data/some/path' #=> Get the file at that path
116
+ # pull_file 'Shenanigans.app/some/file' #=> Get 'some/file' from the install location of Shenanigans.app
117
+ # ```
118
+
119
+ # @!method pull_folder
120
+ # Retrieve a folder from the device.
121
+ # @param [String] path absolute path to the folder
122
+ #
123
+ # ```ruby
124
+ # pull_folder '/data/local/tmp' #=> Get the folder at that path
125
+ # ```
126
+
127
+ # @!method get_settings
128
+ # Get appium Settings for current test session
129
+
130
+ # @!method update_settings
131
+ # Update appium Settings for current test session
132
+ # @param [Hash] settings Settings to update, keys are settings, values to value to set each setting to
133
+
134
+ # @!method set_immediate_value
135
+ # Set the value to element directly
136
+ # for iOS; setValue is called in XCUITest instead because XCUITest doesn't provide set value directly.
137
+ # https://github.com/appium/appium-xcuitest-driver/blob/793cdc7d5e84bd553e375076e1c6dc7e242c9cde/lib/commands/element.js#L123
138
+ #
139
+ # ```ruby
140
+ # set_immediate_value element, 'hello'
141
+ # ```
142
+
143
+ # @!method get_network_connection
144
+ # Get the device network connection current status
145
+ # See set_network_connection method for return value
146
+
147
+ class << self
148
+ def extended(_mod)
149
+ extend_webdriver_with_forwardable
150
+
151
+ ::Appium::Core::Commands::COMMAND_NO_ARG.each_key do |method|
152
+ add_endpoint_method method
153
+ end
154
+
155
+ # call http://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/DriverExtensions/HasRemoteStatus#remote_status-instance_method
156
+ add_endpoint_method(:remote_status) do
157
+ def remote_status
158
+ execute(:status, {}) || []
159
+ end
160
+ end
161
+
162
+ # TODO: Don't define selenium-side methods. We pick up from them.
163
+ # ::Appium::Core::Base::Commands::OSS.each_key do |method|
164
+ # add_endpoint_method method
165
+ # end
166
+
167
+ add_endpoint_method(:available_contexts) do
168
+ def available_contexts
169
+ # return empty array instead of nil on failure
170
+ execute(:available_contexts, {}) || []
171
+ end
172
+ end
173
+
174
+ add_endpoint_method(:app_strings) do
175
+ def app_strings(language = nil)
176
+ opts = language ? { language: language } : {}
177
+ execute :app_strings, {}, opts
178
+ end
179
+ end
180
+
181
+ add_endpoint_method(:lock) do
182
+ def lock(duration)
183
+ execute :lock, {}, seconds: duration
184
+ end
185
+ end
186
+
187
+ add_endpoint_method(:install_app) do
188
+ def install_app(path)
189
+ execute :install_app, {}, appPath: path
190
+ end
191
+ end
192
+
193
+ add_endpoint_method(:remove_app) do
194
+ def remove_app(id)
195
+ execute :remove_app, {}, appId: id
196
+ end
197
+ end
198
+
199
+ add_endpoint_method(:app_installed?) do
200
+ def app_installed?(app_id)
201
+ execute :app_installed?, {}, bundleId: app_id
202
+ end
203
+ end
204
+
205
+ add_endpoint_method(:background_app) do
206
+ def background_app(duration = 0)
207
+ execute :background_app, {}, seconds: duration
208
+ end
209
+ end
210
+
211
+ add_endpoint_method(:set_context) do
212
+ def set_context(context = null)
213
+ execute :set_context, {}, name: context
214
+ end
215
+ end
216
+
217
+ add_endpoint_method(:hide_keyboard) do
218
+ def hide_keyboard(close_key = nil, strategy = nil)
219
+ option = {}
220
+
221
+ option[:key] = close_key || 'Done' # default to Done key.
222
+ option[:strategy] = strategy || :pressKey # default to pressKey
223
+
224
+ execute :hide_keyboard, {}, option
225
+ end
226
+ end
227
+
228
+ add_endpoint_method(:press_keycode) do
229
+ def press_keycode(key, metastate = nil)
230
+ args = { keycode: key }
231
+ args[:metastate] = metastate if metastate
232
+ execute :press_keycode, {}, args
233
+ end
234
+ end
235
+
236
+ add_endpoint_method(:long_press_keycode) do
237
+ def long_press_keycode(key, metastate = nil)
238
+ args = { keycode: key }
239
+ args[:metastate] = metastate if metastate
240
+ execute :long_press_keycode, {}, args
241
+ end
242
+ end
243
+
244
+ add_endpoint_method(:set_immediate_value) do
245
+ def set_immediate_value(element, *value)
246
+ keys = ::Selenium::WebDriver::Keys.encode(value)
247
+ execute :set_immediate_value, { id: element.ref }, value: Array(keys)
248
+ end
249
+ end
250
+
251
+ add_endpoint_method(:push_file) do
252
+ def push_file(path, filedata)
253
+ encoded_data = Base64.encode64 filedata
254
+ execute :push_file, {}, path: path, data: encoded_data
255
+ end
256
+ end
257
+
258
+ add_endpoint_method(:pull_file) do
259
+ def pull_file(path)
260
+ data = execute :pull_file, {}, path: path
261
+ Base64.decode64 data
262
+ end
263
+ end
264
+
265
+ # TODO: TEST ME
266
+ add_endpoint_method(:pull_folder) do
267
+ def pull_folder(path)
268
+ data = execute :pull_folder, {}, path: path
269
+ Base64.decode64 data
270
+ end
271
+ end
272
+
273
+ add_endpoint_method(:get_settings) do
274
+ def get_settings
275
+ execute :get_settings, {}
276
+ end
277
+ end
278
+
279
+ add_endpoint_method(:update_settings) do
280
+ def update_settings(settings)
281
+ execute :update_settings, {}, settings: settings
282
+ end
283
+ end
284
+
285
+ add_touch_actions
286
+ add_ime_actions
287
+ end
288
+
289
+ # def extended
290
+
291
+ # @private
292
+ def add_endpoint_method(method)
293
+ block_given? ? create_bridge_command(method, &Proc.new) : create_bridge_command(method)
294
+
295
+ delegate_driver_method method
296
+ delegate_from_appium_driver method
297
+ end
298
+
299
+ # @private CoreBridge
300
+ def extend_webdriver_with_forwardable
301
+ return if ::Appium::Core::Base::Driver.is_a? Forwardable
302
+ ::Appium::Core::Base::Driver.class_eval do
303
+ extend Forwardable
304
+ end
305
+ end
306
+
307
+ # @private
308
+ def delegate_driver_method(method)
309
+ return if ::Appium::Core::Base::Driver.method_defined? method
310
+ ::Appium::Core::Base::Driver.class_eval { def_delegator :@bridge, method }
311
+ end
312
+
313
+ # @private
314
+ def delegate_from_appium_driver(method, delegation_target = :driver)
315
+ def_delegator delegation_target, method
316
+ end
317
+
318
+ # @private
319
+ def create_bridge_command(method)
320
+ ::Appium::Core::Base::CoreBridgeOSS.class_eval do
321
+ block_given? ? class_eval(&Proc.new) : define_method(method) { execute method }
322
+ end
323
+ ::Appium::Core::Base::CoreBridgeW3C.class_eval do
324
+ block_given? ? class_eval(&Proc.new) : define_method(method) { execute method }
325
+ end
326
+ end
327
+
328
+ def add_touch_actions
329
+ add_endpoint_method(:touch_actions) do
330
+ def touch_actions(actions)
331
+ actions = { actions: [actions].flatten }
332
+ execute :touch_actions, {}, actions
333
+ end
334
+ end
335
+
336
+ add_endpoint_method(:multi_touch) do
337
+ def multi_touch(actions)
338
+ execute :multi_touch, {}, actions: actions
339
+ end
340
+ end
341
+
342
+ actions = Appium::TouchAction::COMPLEX_ACTIONS
343
+ actions.each do |method|
344
+ delegate_from_appium_driver(method, Appium::TouchAction)
345
+ end
346
+
347
+ delegate_from_appium_driver(:pinch, Appium::MultiTouch)
348
+ delegate_from_appium_driver(:zoom, Appium::MultiTouch)
349
+ end
350
+
351
+ def add_ime_actions
352
+ # Commands for IME are defined in the following commands.rb, but the driver have no bridge.
353
+ # So, appium_lib define just bridge here.
354
+ # https://github.com/SeleniumHQ/selenium/blob/selenium-3.0.1/rb/lib/selenium/webdriver/remote/commands.rb#L184-L192
355
+
356
+ # @!method ime_activate
357
+ # Make an engine that is available active.
358
+ #
359
+ # Android only.
360
+ # @param [String] The IME owning the activity [required]
361
+ #
362
+ # ```ruby
363
+ # ime_activate engine: 'com.android.inputmethod.latin/.LatinIME'
364
+ # ```
365
+ add_endpoint_method(:ime_activate) do
366
+ def ime_activate(ime_name)
367
+ # from Selenium::WebDriver::Remote::OSS
368
+ execute :ime_activate_engine, {}, engine: ime_name
369
+ end
370
+ end
371
+
372
+ # @!method ime_available_engines
373
+ # List all available input engines on the machine.
374
+ # Android only.
375
+ #
376
+ # ```ruby
377
+ # ime_available_engines #=> Get the list of IME installed in the target device
378
+ # ```
379
+ add_endpoint_method(:ime_available_engines) do
380
+ def ime_available_engines
381
+ execute :ime_get_available_engines
382
+ end
383
+ end
384
+
385
+ # @!method ime_active_engine
386
+ # Get the name of the active IME engine.
387
+ # Android only.
388
+ #
389
+ # ```ruby
390
+ # ime_active_engine #=> Get the current active IME such as 'com.android.inputmethod.latin/.LatinIME'
391
+ # ```
392
+ add_endpoint_method(:ime_active_engine) do
393
+ # from Selenium::WebDriver::Remote::OSS
394
+ def ime_active_engine
395
+ execute :ime_get_active_engine
396
+ end
397
+ end
398
+
399
+ # @!method ime_activated
400
+ # Indicates whether IME input is active at the moment (not if it is available).
401
+ # Android only.
402
+ #
403
+ # ```ruby
404
+ # ime_activated #=> True if IME is activated
405
+ # ```
406
+ add_endpoint_method(:ime_activated) do
407
+ # from Selenium::WebDriver::Remote::OSS
408
+ def ime_activated
409
+ execute :ime_is_activated
410
+ end
411
+ end
412
+
413
+ # @!method ime_deactivate
414
+ # De-activates the currently-active IME engine.
415
+ #
416
+ # Android only.
417
+ #
418
+ # ```ruby
419
+ # ime_deactivate #=> Deactivate current IME engine
420
+ # ```
421
+ add_endpoint_method(:ime_deactivate) do
422
+ # from Selenium::WebDriver::Remote::OSS
423
+ def ime_deactivate
424
+ execute :ime_deactivate, {}
425
+ end
426
+ end
427
+ end
428
+ end # class << self
429
+
430
+ # @!method set_context
431
+ # Change the context to the given context.
432
+ # @param [String] The context to change to
433
+ #
434
+ # ```ruby
435
+ # set_context "NATIVE_APP"
436
+ # ```
437
+
438
+ # @!method current_context
439
+ # @return [String] The context currently being used.
440
+
441
+ # @!method available_contexts
442
+ # @return [Array<String>] All usable contexts, as an array of strings.
443
+
444
+ # Perform a block within the given context, then switch back to the starting context.
445
+ # @param context (String) The context to switch to for the duration of the block.
446
+ #
447
+ # ```ruby
448
+ # result = within_context('NATIVE_APP') do
449
+ # find_element :tag, "button"
450
+ # end # The result of `find_element :tag, "button"`
451
+ # ```
452
+ def within_context(context)
453
+ existing_context = current_context
454
+ set_context context
455
+ if block_given?
456
+ result = yield
457
+ set_context existing_context
458
+ result
459
+ else
460
+ set_context existing_context
461
+ end
462
+ end
463
+
464
+ # Change to the default context. This is equivalent to `set_context nil`.
465
+ def switch_to_default_context
466
+ set_context nil
467
+ end
468
+ end # module Device
469
+ end # Core
470
+ end # module Appium