appium_lib_core 4.1.0 → 5.0.0.rc4

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/unittest.yml +2 -2
  4. data/CHANGELOG.md +54 -277
  5. data/README.md +2 -1
  6. data/Rakefile +4 -0
  7. data/appium_lib_core.gemspec +4 -4
  8. data/ci-jobs/functional/run_appium.yml +2 -2
  9. data/ci-jobs/functional_test.yml +2 -2
  10. data/docs/mobile_command.md +2 -2
  11. data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
  12. data/lib/appium_lib_core/android/device.rb +4 -4
  13. data/lib/appium_lib_core/common/base/bridge.rb +298 -89
  14. data/lib/appium_lib_core/common/base/capabilities.rb +10 -3
  15. data/lib/appium_lib_core/common/base/device_ime.rb +49 -0
  16. data/lib/appium_lib_core/common/base/driver.rb +149 -128
  17. data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
  18. data/lib/appium_lib_core/common/base/has_location.rb +80 -0
  19. data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
  20. data/lib/appium_lib_core/common/base/http_default.rb +1 -3
  21. data/lib/appium_lib_core/common/base/remote_status.rb +31 -0
  22. data/lib/appium_lib_core/common/base/rotable.rb +54 -0
  23. data/lib/appium_lib_core/common/base/screenshot.rb +4 -4
  24. data/lib/appium_lib_core/common/base/search_context.rb +11 -4
  25. data/lib/appium_lib_core/common/base.rb +1 -3
  26. data/lib/appium_lib_core/common/command.rb +259 -4
  27. data/lib/appium_lib_core/common/device/image_comparison.rb +12 -4
  28. data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
  29. data/lib/appium_lib_core/common/{command/mjsonwp.rb → device/orientation.rb} +14 -11
  30. data/lib/appium_lib_core/common/device/value.rb +4 -4
  31. data/lib/appium_lib_core/common/error.rb +4 -1
  32. data/lib/appium_lib_core/common/log.rb +4 -1
  33. data/lib/appium_lib_core/common/touch_action/touch_actions.rb +1 -1
  34. data/lib/appium_lib_core/device.rb +1 -5
  35. data/lib/appium_lib_core/driver.rb +26 -45
  36. data/lib/appium_lib_core/{patch.rb → element.rb} +66 -9
  37. data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
  38. data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
  39. data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
  40. data/lib/appium_lib_core/mac2/device.rb +92 -0
  41. data/lib/appium_lib_core/mac2.rb +17 -0
  42. data/lib/appium_lib_core/version.rb +2 -2
  43. data/lib/appium_lib_core.rb +2 -5
  44. data/release_notes.md +82 -0
  45. data/script/commands.rb +3 -37
  46. metadata +28 -28
  47. data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -81
  48. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -252
  49. data/lib/appium_lib_core/common/command/common.rb +0 -110
  50. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
data/README.md CHANGED
@@ -11,6 +11,7 @@ This library wraps [selenium-webdriver](https://github.com/SeleniumHQ/selenium/w
11
11
  # Documentation
12
12
 
13
13
  - http://www.rubydoc.info/github/appium/ruby_lib_core
14
+ - You can find working API examples in test code, [test/functional](test/functional)
14
15
 
15
16
  # Related library
16
17
  - https://github.com/appium/ruby_lib
@@ -120,7 +121,7 @@ $ IGNORE_VERSION_SKIP=true CI=true bundle exec rake test:func:android
120
121
  $ ruby test.rb
121
122
  ```
122
123
 
123
- More examples are in [test/functional](test/functional)
124
+ More examples are in [test/functional](test/functional)
124
125
 
125
126
  ### Capabilities
126
127
 
data/Rakefile CHANGED
@@ -38,6 +38,7 @@ namespace :test do
38
38
  namespace :unit do
39
39
  desc('Run all iOS related unit tests in test directory')
40
40
  Rake::TestTask.new(:ios) do |t|
41
+ ENV['UNIT_TEST'] = '1'
41
42
  t.libs << 'test'
42
43
  t.libs << 'lib'
43
44
  t.test_files = FileList['test/unit/ios/**/*_test.rb']
@@ -45,6 +46,7 @@ namespace :test do
45
46
 
46
47
  desc('Run all Android related unit tests in test directory')
47
48
  Rake::TestTask.new(:android) do |t|
49
+ ENV['UNIT_TEST'] = '1'
48
50
  t.libs << 'test'
49
51
  t.libs << 'lib'
50
52
  t.test_files = FileList['test/unit/android/**/*_test.rb']
@@ -52,6 +54,7 @@ namespace :test do
52
54
 
53
55
  desc('Run all common related unit tests in test directory')
54
56
  Rake::TestTask.new(:common) do |t|
57
+ ENV['UNIT_TEST'] = '1'
55
58
  t.libs << 'test'
56
59
  t.libs << 'lib'
57
60
  t.test_files = FileList['test/unit/common/**/*_test.rb']
@@ -59,6 +62,7 @@ namespace :test do
59
62
 
60
63
  desc('Run all Windows related unit tests in test directory')
61
64
  Rake::TestTask.new(:windows) do |t|
65
+ ENV['UNIT_TEST'] = '1'
62
66
  t.libs << 'test'
63
67
  t.libs << 'lib'
64
68
  t.test_files = FileList['test/unit/windows/**/*_test.rb']
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'appium_lib_core/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.required_ruby_version = '>= 2.4'
7
+ spec.required_ruby_version = '>= 2.5'
8
8
 
9
9
  spec.name = 'appium_lib_core'
10
10
  spec.version = Appium::Core::VERSION
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_runtime_dependency 'selenium-webdriver', '~> 3.14', '>= 3.14.1'
25
+ spec.add_runtime_dependency 'selenium-webdriver', '~> 4.0.0'
26
26
  spec.add_runtime_dependency 'faye-websocket', '~> 0.11.0'
27
27
 
28
28
  spec.add_development_dependency 'bundler', '>= 1.14'
@@ -30,8 +30,8 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'yard', '~> 0.9.11'
31
31
  spec.add_development_dependency 'minitest', '~> 5.0'
32
32
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
33
- spec.add_development_dependency 'webmock', '~> 3.11.0'
34
- spec.add_development_dependency 'rubocop', '1.7.0'
33
+ spec.add_development_dependency 'webmock', '~> 3.14.0'
34
+ spec.add_development_dependency 'rubocop', '1.12.1'
35
35
  spec.add_development_dependency 'appium_thor', '~> 1.0'
36
36
  spec.add_development_dependency 'pry'
37
37
  spec.add_development_dependency 'pry-byebug'
@@ -1,8 +1,8 @@
1
1
  steps:
2
2
  - task: NodeTool@0
3
3
  inputs:
4
- versionSpec: 10.x
5
- displayName: Install Node 10.x
4
+ versionSpec: 12.x
5
+ displayName: Install Node 12.x
6
6
  - script: npm install -g appium@${APPIUM_VERSION}
7
7
  displayName: Install appium beta
8
8
  - script: npm install -g mjpeg-consumer
@@ -4,7 +4,7 @@ parameters:
4
4
  vmImageForIOS: 'macOS-10.15' # Not sure the reason, but macOS 10.14 instance raises no info.plist error
5
5
  xcodeForIOS: 12.2
6
6
  xcodeForTVOS: 12.2
7
- androidSDK: 30
7
+ androidSDK: 29 # API Level 30 emulators are more unstable than 29
8
8
  appiumVersion: 'beta'
9
9
  ignoreVersionSkip: true
10
10
  CI: true
@@ -103,7 +103,7 @@ jobs:
103
103
  - template: ./functional/ios_setup.yml
104
104
  parameters:
105
105
  xcodeVersion: ${{ parameters.xcodeForIOS }}
106
- - script: brew install ffmpeg && brew tap wix/brew && brew install wix/brew/applesimutils
106
+ - script: brew install ffmpeg && brew tap wix/brew && brew install applesimutils
107
107
  displayName: Install ffmpeg and applesimutils
108
108
  - template: ./functional/run_appium.yml
109
109
  - script: bundle exec rake test:func:ios TESTS=test/functional/ios/ios/mjpeg_server_test.rb,test/functional/ios/ios/mobile_commands_test.rb
@@ -9,7 +9,7 @@ We can invoke them via `execute_script` command with `mobile:` arguments.
9
9
  - https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android
10
10
  - iOS:
11
11
  - https://github.com/appium/appium/tree/master/docs/en/writing-running-appium/ios
12
-
12
+
13
13
 
14
14
  ```ruby
15
15
  # Android shell : https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-shell.md
@@ -28,7 +28,7 @@ To handle it, we would recommend you to handle the error based on the error mess
28
28
 
29
29
  ```ruby
30
30
  error = assert_raises ::Selenium::WebDriver::Error::UnknownError do
31
- @driver.execute_script 'mobile: scrollToPage', { element: el.ref, scrollToPage: -100 }
31
+ @driver.execute_script 'mobile: scrollToPage', { element: el.id, scrollToPage: -100 }
32
32
  end
33
33
  assert error.message.include? 'be a non-negative integer'
34
34
  ```
@@ -21,7 +21,8 @@ module Appium
21
21
  ::Appium::Core::Device.add_endpoint_method(:finger_print) do
22
22
  def finger_print(finger_id)
23
23
  unless (1..10).cover? finger_id.to_i
24
- raise ArgumentError, "finger_id should be integer between 1 to 10. Not #{finger_id}"
24
+ raise ::Appium::Core::Error::ArgumentError,
25
+ "finger_id should be integer between 1 to 10. Not #{finger_id}"
25
26
  end
26
27
 
27
28
  execute(:finger_print, {}, { fingerprintId: finger_id.to_i })
@@ -290,16 +290,16 @@ module Appium
290
290
  # @driver.finger_print 1
291
291
  #
292
292
 
293
- # @!method execute_cdp(cmd, params)
293
+ # @!method execute_cdp(cmd, **params)
294
294
  # Execute Chrome Devtools protocol commands
295
295
  # https://chromedevtools.github.io/devtools-protocol
296
296
  #
297
297
  # @param [String] cmd The name of command
298
- # @param [Hash] params The parameter for the command as hash.
298
+ # @option params The parameter for the command as keyword options.
299
299
  #
300
300
  # @example
301
301
  #
302
- # @driver.execute_cdp 'Page.captureScreenshot', { quality: 50, format: 'jpeg' }
302
+ # @driver.execute_cdp 'Page.captureScreenshot', quality: 50, format: 'jpeg'
303
303
  # @driver.execute_cdp 'Page.getResourceTree'
304
304
  #
305
305
 
@@ -415,7 +415,7 @@ module Appium
415
415
  # SeleniumWebdriver could already define this method
416
416
  return if method_defined? :execute_cdp
417
417
 
418
- def execute_cdp(cmd, params = {})
418
+ def execute_cdp(cmd, **params)
419
419
  execute :chrome_send_command, {}, { cmd: cmd, params: params }
420
420
  end
421
421
  end
@@ -16,52 +16,45 @@ module Appium
16
16
  module Core
17
17
  class Base
18
18
  class Bridge < ::Selenium::WebDriver::Remote::Bridge
19
+ include Device::DeviceLock
20
+ include Device::Keyboard
21
+ include Device::ImeActions
22
+ include Device::Setting
23
+ include Device::Context
24
+ include Device::Value
25
+ include Device::FileManagement
26
+ include Device::KeyEvent
27
+ include Device::ImageComparison
28
+ include Device::AppManagement
29
+ include Device::AppState
30
+ include Device::ScreenRecord::Command
31
+ include Device::Device
32
+ include Device::TouchActions
33
+ include Device::ExecuteDriver
34
+ include Device::Orientation
35
+
19
36
  # Prefix for extra capability defined by W3C
20
37
  APPIUM_PREFIX = 'appium:'
21
38
 
22
- # TODO: Remove the forceMjsonwp after Appium server won't need it
23
- FORCE_MJSONWP = :forceMjsonwp
24
-
25
- # Almost same as self.handshake in ::Selenium::WebDriver::Remote::Bridge
26
- #
27
- # Implements protocol handshake which:
28
- #
29
- # 1. Creates session with driver.
30
- # 2. Sniffs response.
31
- # 3. Based on the response, understands which dialect we should use.
32
- #
33
- # @return [Bridge::MJSONWP, Bridge::W3C]
34
- #
35
- def self.handshake(**opts)
36
- desired_capabilities = opts.delete(:desired_capabilities) { ::Selenium::WebDriver::Remote::Capabilities.new }
37
-
38
- if desired_capabilities.is_a?(Symbol)
39
- unless ::Selenium::WebDriver::Remote::Capabilities.respond_to?(desired_capabilities)
40
- raise ::Selenium::WebDriver::Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
41
- end
42
-
43
- desired_capabilities = ::Selenium::WebDriver::Remote::Capabilities.__send__(desired_capabilities)
44
- end
39
+ # No 'browserName' means the session is native appium connection
40
+ APPIUM_NATIVE_BROWSER_NAME = 'appium'
45
41
 
46
- bridge = new(opts)
47
- capabilities = bridge.create_session(desired_capabilities)
42
+ attr_reader :available_commands
48
43
 
49
- case bridge.dialect
50
- when :oss # for MJSONWP
51
- Bridge::MJSONWP.new(capabilities, bridge.session_id, **opts)
52
- when :w3c
53
- Bridge::W3C.new(capabilities, bridge.session_id, **opts)
54
- else
55
- raise CoreError, 'cannot understand dialect'
44
+ def browser
45
+ @browser ||= begin
46
+ name = @capabilities.browser_name
47
+ name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
48
+ rescue KeyError
49
+ APPIUM_NATIVE_BROWSER_NAME
56
50
  end
57
51
  end
58
52
 
59
53
  # Override
60
54
  # Creates session handling both OSS and W3C dialects.
61
- # Copy from Selenium::WebDriver::Remote::Bridge to keep using +merged_capabilities+ for Appium
62
55
  #
63
- # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] desired_capabilities A capability
64
- # @return [::Selenium::WebDriver::Remote::Capabilities, ::Selenium::WebDriver::Remote::W3C::Capabilities]
56
+ # @param [::Selenium::WebDriver::Remote::Capabilities, Hash] capabilities A capability
57
+ # @return [::Selenium::WebDriver::Remote::Capabilities]
65
58
  #
66
59
  # @example
67
60
  #
@@ -79,39 +72,29 @@ module Appium
79
72
  # }
80
73
  # }
81
74
  # core = ::Appium::Core.for(caps)
82
- # driver = core.start_driver #=> driver.dialect == :w3c if the Appium server support W3C.
75
+ # driver = core.start_driver
83
76
  #
84
- def create_session(desired_capabilities)
85
- response = execute(:new_session, {}, merged_capabilities(desired_capabilities))
77
+ def create_session(capabilities)
78
+ @available_commands = ::Appium::Core::Commands::COMMANDS.dup
86
79
 
87
- @session_id = response['sessionId']
88
- oss_status = response['status'] # for compatibility with Appium 1.7.1-
89
- value = response['value']
90
-
91
- if value.is_a?(Hash) # include for W3C format
92
- @session_id = value['sessionId'] if value.key?('sessionId')
93
-
94
- if value.key?('capabilities')
95
- value = value['capabilities']
96
- elsif value.key?('value')
97
- value = value['value']
98
- end
99
- end
80
+ caps = add_appium_prefix(capabilities)
81
+ response = execute(:new_session, {}, { capabilities: { alwaysMatch: caps, firstMatch: [] } })
100
82
 
83
+ @session_id = response['sessionId']
101
84
  raise ::Selenium::WebDriver::Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
102
85
 
103
- json_create(oss_status, value)
86
+ @capabilities = json_create(response['capabilities'])
104
87
  end
105
88
 
106
89
  # Append +appium:+ prefix for Appium following W3C spec
107
90
  # https://www.w3.org/TR/webdriver/#dfn-validate-capabilities
108
91
  #
109
- # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] capabilities A capability
110
- # @return [::Selenium::WebDriver::Remote::W3C::Capabilities]
92
+ # @param [::Selenium::WebDriver::Remote::Capabilities, Hash] capabilities A capability
93
+ # @return [::Selenium::WebDriver::Remote::Capabilities]
111
94
  def add_appium_prefix(capabilities)
112
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new
95
+ w3c_capabilities = ::Selenium::WebDriver::Remote::Capabilities.new
113
96
 
114
- capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)
97
+ capabilities = capabilities.send(:capabilities) unless capabilities.is_a?(Hash)
115
98
 
116
99
  capabilities.each do |name, value|
117
100
  next if value.nil?
@@ -133,56 +116,282 @@ module Appium
133
116
  end
134
117
 
135
118
  def extension_prefix?(capability_name)
136
- snake_cased_capability_names = ::Selenium::WebDriver::Remote::W3C::Capabilities::KNOWN.map(&:to_s)
119
+ snake_cased_capability_names = ::Selenium::WebDriver::Remote::Capabilities::KNOWN.map(&:to_s)
137
120
  camel_cased_capability_names = snake_cased_capability_names.map { |v| camel_case(v) }
138
121
 
122
+ # Check 'EXTENSION_CAPABILITY_PATTERN'
139
123
  snake_cased_capability_names.include?(capability_name) ||
140
124
  camel_cased_capability_names.include?(capability_name) ||
141
- capability_name.match(::Selenium::WebDriver::Remote::W3C::Capabilities::EXTENSION_CAPABILITY_PATTERN)
125
+ capability_name.match(':')
142
126
  end
143
127
 
144
- def json_create(oss_status, value)
145
- if oss_status
146
- ::Selenium::WebDriver.logger.info 'Detected OSS dialect.'
147
- @dialect = :oss
148
- ::Selenium::WebDriver::Remote::Capabilities.json_create(value)
149
- else
150
- ::Selenium::WebDriver.logger.info 'Detected W3C dialect.'
151
- @dialect = :w3c
152
- ::Selenium::WebDriver::Remote::W3C::Capabilities.json_create(value)
153
- end
128
+ def json_create(value)
129
+ ::Selenium::WebDriver::Remote::Capabilities.json_create(value)
154
130
  end
155
131
 
156
- def delete_force_mjsonwp(capabilities)
157
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new
132
+ public
158
133
 
159
- capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)
160
- capabilities.each do |name, value|
161
- next if value.nil?
162
- next if value.is_a?(String) && value.empty?
163
- next if name == FORCE_MJSONWP
134
+ # command for Appium 2.0.
135
+ def add_command(method:, url:, name:, &block)
136
+ ::Appium::Logger.info "Overriding the method '#{name}' for '#{url}'" if @available_commands.key? name
137
+
138
+ @available_commands[name] = [method, url]
164
139
 
165
- w3c_capabilities[name] = value
140
+ ::Appium::Core::Device.add_endpoint_method name, &block
141
+ end
142
+
143
+ def commands(command)
144
+ @available_commands[command]
145
+ end
146
+
147
+ # Returns all available sessions on the Appium server instance
148
+ def sessions
149
+ execute :get_all_sessions
150
+ end
151
+
152
+ def status
153
+ execute :status
154
+ end
155
+
156
+ # Perform touch actions for W3C module.
157
+ # Generate +touch+ pointer action here and users can use this via +driver.action+
158
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/W3CActionBuilder.html
159
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/PointerActions.html
160
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/KeyActions.html
161
+ #
162
+ # 'mouse' action is by default in the Ruby client. Appium server force the +mouse+ action to +touch+ once in
163
+ # the server side. So we don't consider the case.
164
+ #
165
+ # @example
166
+ #
167
+ # element = @driver.find_element(:id, "some id")
168
+ # @driver.action.click(element).perform # The 'click' is a part of 'PointerActions'
169
+ #
170
+ def action(async = false)
171
+ # Used for default duration of each touch actions
172
+ # Override from 250 milliseconds to 50 milliseconds
173
+ action_builder = super
174
+ action_builder.default_move_duration = 0.05
175
+ action_builder
176
+ end
177
+
178
+ # Port from MJSONWP
179
+ def get_timeouts
180
+ execute :get_timeouts
181
+ end
182
+
183
+ # Port from MJSONWP
184
+ def session_capabilities
185
+ ::Selenium::WebDriver::Remote::Capabilities.json_create execute(:get_capabilities)
186
+ end
187
+
188
+ # Port from MJSONWP
189
+ def send_keys_to_active_element(key)
190
+ text = ::Selenium::WebDriver::Keys.encode(key).join('')
191
+ execute :send_keys_to_active_element, {}, { value: text.split(//) }
192
+ end
193
+
194
+ # For Appium
195
+ # override
196
+ def page_source
197
+ # For W3C
198
+ # execute_script('var source = document.documentElement.outerHTML;' \
199
+ # 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
200
+ # 'return source;')
201
+ execute :get_page_source
202
+ end
203
+
204
+ # For Appium
205
+ # override
206
+ def element_displayed?(element)
207
+ # For W3C
208
+ # https://github.com/SeleniumHQ/selenium/commit/b618499adcc3a9f667590652c5757c0caa703289
209
+ # execute_atom :isDisplayed, element
210
+ execute :is_element_displayed, id: element.id
211
+ end
212
+
213
+ # For Appium
214
+ # override
215
+ def element_attribute(element, name)
216
+ # For W3C in Selenium Client
217
+ # execute_atom :getAttribute, element, name
218
+ execute :get_element_attribute, id: element.id, name: name
219
+ end
220
+
221
+ # For Appium
222
+ # override
223
+ def active_element
224
+ ::Appium::Core::Element.new self, element_id_from(execute(:get_active_element))
225
+ end
226
+ alias switch_to_active_element active_element
227
+
228
+ # For Appium
229
+ # override
230
+ def find_element_by(how, what, parent_ref = [])
231
+ how, what = convert_locator(how, what)
232
+
233
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
234
+
235
+ parent_type, parent_id = parent_ref
236
+ id = case parent_type
237
+ when :element
238
+ execute :find_child_element, { id: parent_id }, { using: how, value: what.to_s }
239
+ when :shadow_root
240
+ execute :find_shadow_child_element, { id: parent_id }, { using: how, value: what.to_s }
241
+ else
242
+ execute :find_element, {}, { using: how, value: what.to_s }
243
+ end
244
+
245
+ ::Appium::Core::Element.new self, element_id_from(id)
246
+ end
247
+
248
+ # For Appium
249
+ # override
250
+ def find_elements_by(how, what, parent_ref = [])
251
+ how, what = convert_locator(how, what)
252
+
253
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
254
+
255
+ parent_type, parent_id = parent_ref
256
+ ids = case parent_type
257
+ when :element
258
+ execute :find_child_elements, { id: parent_id }, { using: how, value: what.to_s }
259
+ when :shadow_root
260
+ execute :find_shadow_child_elements, { id: parent_id }, { using: how, value: what.to_s }
261
+ else
262
+ execute :find_elements, {}, { using: how, value: what.to_s }
263
+ end
264
+
265
+ ids.map { |id| ::Appium::Core::Element.new self, element_id_from(id) }
266
+ end
267
+
268
+ # For Appium
269
+ # @param [Hash] id The id which can get as a response from server
270
+ # @return [::Appium::Core::Element]
271
+ def convert_to_element(id)
272
+ ::Appium::Core::Element.new self, element_id_from(id)
273
+ end
274
+
275
+ # For Appium
276
+ # override
277
+ # called in 'extend DriverExtensions::HasNetworkConnection'
278
+ def network_connection
279
+ execute :get_network_connection
280
+ end
281
+
282
+ # For Appium
283
+ # override
284
+ # called in 'extend DriverExtensions::HasNetworkConnection'
285
+ def network_connection=(type)
286
+ execute :set_network_connection, {}, { parameters: { type: type } }
287
+ end
288
+
289
+ # For Appium
290
+ # No implementation for W3C webdriver module
291
+ # called in 'extend DriverExtensions::HasLocation'
292
+ def location
293
+ obj = execute(:get_location) || {}
294
+ ::Selenium::WebDriver::Location.new obj['latitude'], obj['longitude'], obj['altitude']
295
+ end
296
+
297
+ # For Appium
298
+ # No implementation for W3C webdriver module
299
+ def set_location(lat, lon, alt = 0.0, speed: nil, satellites: nil)
300
+ loc = { latitude: lat, longitude: lon, altitude: alt }
301
+ loc[:speed] = speed unless speed.nil?
302
+ loc[:satellites] = satellites unless satellites.nil?
303
+ execute :set_location, {}, { location: loc }
304
+ end
305
+
306
+ #
307
+ # logs
308
+ #
309
+ # For Appium
310
+ # No implementation for W3C webdriver module
311
+ def available_log_types
312
+ types = execute :get_available_log_types
313
+ Array(types).map(&:to_sym)
314
+ end
315
+
316
+ # For Appium
317
+ # No implementation for W3C webdriver module
318
+ def log(type)
319
+ data = execute :get_log, {}, { type: type.to_s }
320
+
321
+ Array(data).map do |l|
322
+ begin
323
+ ::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
324
+ rescue KeyError
325
+ next
326
+ end
166
327
  end
328
+ end
167
329
 
168
- w3c_capabilities
330
+ # For Appium
331
+ def log_event(vendor, event)
332
+ execute :post_log_event, {}, { vendor: vendor, event: event }
333
+ end
334
+
335
+ # For Appium
336
+ def log_events(type = nil)
337
+ args = {}
338
+ args['type'] = type unless type.nil?
339
+
340
+ execute :get_log_events, {}, args
341
+ end
342
+
343
+ def viewport_screenshot
344
+ execute_script('mobile: viewportScreenshot')
169
345
  end
170
346
 
171
- def merged_capabilities(desired_capabilities)
172
- force_mjsonwp = desired_capabilities[FORCE_MJSONWP]
173
- desired_capabilities = delete_force_mjsonwp(desired_capabilities) unless force_mjsonwp.nil?
347
+ def element_screenshot(element_id)
348
+ execute :take_element_screenshot, id: element_id
349
+ end
350
+
351
+ private
174
352
 
175
- ::Appium::Logger.warn "'forceMjsonwp' no longer works. Sending both W3C and MJSONWP capabilities" if force_mjsonwp
353
+ def unwrap_script_result(arg)
354
+ case arg
355
+ when Array
356
+ arg.map { |e| unwrap_script_result(e) }
357
+ when Hash
358
+ element_id = element_id_from(arg)
359
+ return ::Appium::Core::Element.new(self, element_id) if element_id
176
360
 
177
- new_caps = add_appium_prefix(desired_capabilities)
178
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.from_oss(new_caps)
361
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
362
+ else
363
+ arg
364
+ end
365
+ end
366
+
367
+ def element_id_from(id)
368
+ id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
369
+ end
179
370
 
180
- {
181
- desiredCapabilities: desired_capabilities,
182
- capabilities: {
183
- firstMatch: [w3c_capabilities]
184
- }
185
- }
371
+ # Don't convert locators for Appium in native context
372
+ def convert_locator(how, what)
373
+ # case how
374
+ # when 'class name'
375
+ # how = 'css selector'
376
+ # what = ".#{escape_css(what)}"
377
+ # when 'id'
378
+ # how = 'css selector'
379
+ # what = "##{escape_css(what)}"
380
+ # when 'name'
381
+ # how = 'css selector'
382
+ # what = "*[name='#{escape_css(what)}']"
383
+ # when 'tag name'
384
+ # how = 'css selector'
385
+ # end
386
+ #
387
+ # if what.is_a?(Hash)
388
+ # what = what.each_with_object({}) do |(h, w), hash|
389
+ # h, w = convert_locator(h.to_s, w)
390
+ # hash[h] = w
391
+ # end
392
+ # end
393
+
394
+ [how, what]
186
395
  end
187
396
  end # class Bridge
188
397
  end # class Base
@@ -19,10 +19,17 @@ module Appium
19
19
  # @private
20
20
  # @param [Hash] opts_caps Capabilities for Appium server. All capability keys are converted to lowerCamelCase when
21
21
  # this client sends capabilities to Appium server as JSON format.
22
- # @return [::Selenium::WebDriver::Remote::W3C::Capabilities] Return instance of Appium::Core::Base::Capabilities
23
- # inherited ::Selenium::WebDriver::Remote::W3C::Capabilities
22
+ # @return [::Selenium::WebDriver::Remote::Capabilities] Return instance of Appium::Core::Base::Capabilities
23
+ # inherited ::Selenium::WebDriver::Remote::Capabilities
24
24
  def self.create_capabilities(opts_caps = {})
25
- ::Selenium::WebDriver::Remote::W3C::Capabilities.new(opts_caps)
25
+ # TODO: Move to 'Options' way instead of 'Capabilities'.
26
+ # Selenium 5 will have Options instead of 'Capabilities'.
27
+ # https://github.com/SeleniumHQ/selenium/blob/trunk/rb/lib/selenium/webdriver/common/options.rb
28
+ # Then, Ruby client also shoud move to the Options way.
29
+ # Appium's capabilities could change by depending on Appium versions. So it does not have
30
+ # standard options like chrome and firefox etc. So, the implementation should differ from
31
+ # other browsers. But here should inherit `Options` to follow Selenium.
32
+ ::Selenium::WebDriver::Remote::Capabilities.new(opts_caps)
26
33
  end
27
34
  end
28
35
  end