appium_lib_core 4.1.0 → 5.0.0.beta2

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/.github/dependabot.yml +8 -0
  3. data/.github/workflows/unittest.yml +2 -2
  4. data/CHANGELOG.md +40 -275
  5. data/appium_lib_core.gemspec +4 -4
  6. data/ci-jobs/functional/run_appium.yml +2 -2
  7. data/ci-jobs/functional_test.yml +1 -1
  8. data/lib/appium_lib_core/android/device.rb +4 -4
  9. data/lib/appium_lib_core/common/base/bridge.rb +263 -88
  10. data/lib/appium_lib_core/common/base/capabilities.rb +3 -3
  11. data/lib/appium_lib_core/common/base/driver.rb +92 -37
  12. data/lib/appium_lib_core/common/base/has_location.rb +73 -0
  13. data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
  14. data/lib/appium_lib_core/common/base/http_default.rb +1 -3
  15. data/lib/appium_lib_core/common/base/remote_status.rb +31 -0
  16. data/lib/appium_lib_core/common/base/rotable.rb +54 -0
  17. data/lib/appium_lib_core/common/base/screenshot.rb +1 -1
  18. data/lib/appium_lib_core/common/base/search_context.rb +2 -0
  19. data/lib/appium_lib_core/common/base.rb +1 -3
  20. data/lib/appium_lib_core/common/command.rb +259 -4
  21. data/lib/appium_lib_core/common/device/image_comparison.rb +12 -4
  22. data/lib/appium_lib_core/common/{command/mjsonwp.rb → device/orientation.rb} +14 -11
  23. data/lib/appium_lib_core/device.rb +1 -5
  24. data/lib/appium_lib_core/driver.rb +18 -13
  25. data/lib/appium_lib_core/{patch.rb → element.rb} +53 -5
  26. data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
  27. data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
  28. data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
  29. data/lib/appium_lib_core/mac2/device.rb +92 -0
  30. data/lib/appium_lib_core/mac2.rb +17 -0
  31. data/lib/appium_lib_core/version.rb +2 -2
  32. data/lib/appium_lib_core.rb +1 -4
  33. data/release_notes.md +59 -0
  34. data/script/commands.rb +3 -37
  35. metadata +24 -26
  36. data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -81
  37. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -252
  38. data/lib/appium_lib_core/common/command/common.rb +0 -110
  39. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
@@ -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.beta2'
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.12.1'
34
+ spec.add_development_dependency 'rubocop', '1.12.0'
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
@@ -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,41 @@ 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
45
-
46
- bridge = new(opts)
47
- capabilities = bridge.create_session(desired_capabilities)
48
-
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'
39
+ # Returns 'unknown' if no specific browser name
40
+ def browser
41
+ @browser ||= begin
42
+ name = @capabilities.browser_name
43
+ name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
44
+ rescue KeyError
45
+ 'unknown'
56
46
  end
57
47
  end
58
48
 
59
49
  # Override
60
50
  # Creates session handling both OSS and W3C dialects.
61
- # Copy from Selenium::WebDriver::Remote::Bridge to keep using +merged_capabilities+ for Appium
62
51
  #
63
- # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] desired_capabilities A capability
64
- # @return [::Selenium::WebDriver::Remote::Capabilities, ::Selenium::WebDriver::Remote::W3C::Capabilities]
52
+ # @param [::Selenium::WebDriver::Remote::Capabilities, Hash] desired_capabilities A capability
53
+ # @return [::Selenium::WebDriver::Remote::Capabilities]
65
54
  #
66
55
  # @example
67
56
  #
@@ -79,37 +68,27 @@ module Appium
79
68
  # }
80
69
  # }
81
70
  # core = ::Appium::Core.for(caps)
82
- # driver = core.start_driver #=> driver.dialect == :w3c if the Appium server support W3C.
71
+ # driver = core.start_driver
83
72
  #
84
73
  def create_session(desired_capabilities)
85
- response = execute(:new_session, {}, merged_capabilities(desired_capabilities))
74
+ caps = add_appium_prefix(desired_capabilities)
75
+ response = execute(:new_session, {}, { capabilities: { firstMatch: [caps] } })
86
76
 
87
77
  @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
78
+ capabilities = response['capabilities']
100
79
 
101
80
  raise ::Selenium::WebDriver::Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
102
81
 
103
- json_create(oss_status, value)
82
+ @capabilities = json_create(capabilities)
104
83
  end
105
84
 
106
85
  # Append +appium:+ prefix for Appium following W3C spec
107
86
  # https://www.w3.org/TR/webdriver/#dfn-validate-capabilities
108
87
  #
109
- # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] capabilities A capability
110
- # @return [::Selenium::WebDriver::Remote::W3C::Capabilities]
88
+ # @param [::Selenium::WebDriver::Remote::Capabilities, Hash] capabilities A capability
89
+ # @return [::Selenium::WebDriver::Remote::Capabilities]
111
90
  def add_appium_prefix(capabilities)
112
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new
91
+ w3c_capabilities = ::Selenium::WebDriver::Remote::Capabilities.new
113
92
 
114
93
  capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)
115
94
 
@@ -133,56 +112,252 @@ module Appium
133
112
  end
134
113
 
135
114
  def extension_prefix?(capability_name)
136
- snake_cased_capability_names = ::Selenium::WebDriver::Remote::W3C::Capabilities::KNOWN.map(&:to_s)
115
+ snake_cased_capability_names = ::Selenium::WebDriver::Remote::Capabilities::KNOWN.map(&:to_s)
137
116
  camel_cased_capability_names = snake_cased_capability_names.map { |v| camel_case(v) }
138
117
 
118
+ # Check 'EXTENSION_CAPABILITY_PATTERN'
139
119
  snake_cased_capability_names.include?(capability_name) ||
140
120
  camel_cased_capability_names.include?(capability_name) ||
141
- capability_name.match(::Selenium::WebDriver::Remote::W3C::Capabilities::EXTENSION_CAPABILITY_PATTERN)
121
+ capability_name.match(':')
142
122
  end
143
123
 
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
124
+ def json_create(value)
125
+ ::Selenium::WebDriver::Remote::Capabilities.json_create(value)
154
126
  end
155
127
 
156
- def delete_force_mjsonwp(capabilities)
157
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new
128
+ public
158
129
 
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
130
+ def commands(command)
131
+ ::Appium::Core::Commands::COMMANDS[command]
132
+ end
133
+
134
+ # Returns all available sessions on the Appium server instance
135
+ def sessions
136
+ execute :get_all_sessions
137
+ end
138
+
139
+ def status
140
+ execute :status
141
+ end
142
+
143
+ # Perform touch actions for W3C module.
144
+ # Generate +touch+ pointer action here and users can use this via +driver.action+
145
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/W3CActionBuilder.html
146
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/PointerActions.html
147
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/KeyActions.html
148
+ #
149
+ # 'mouse' action is by default in the Ruby client. Appium server force the +mouse+ action to +touch+ once in
150
+ # the server side. So we don't consider the case.
151
+ #
152
+ # @example
153
+ #
154
+ # element = @driver.find_element(:id, "some id")
155
+ # @driver.action.click(element).perform # The 'click' is a part of 'PointerActions'
156
+ #
157
+ def action(async = false)
158
+ # Used for default duration of each touch actions
159
+ # Override from 250 milliseconds to 50 milliseconds
160
+ action_builder = super
161
+ action_builder.default_move_duration = 0.05
162
+ action_builder
163
+ end
164
+
165
+ # Port from MJSONWP
166
+ def get_timeouts
167
+ execute :get_timeouts
168
+ end
169
+
170
+ # Port from MJSONWP
171
+ def session_capabilities
172
+ ::Selenium::WebDriver::Remote::Capabilities.json_create execute(:get_capabilities)
173
+ end
174
+
175
+ # Port from MJSONWP
176
+ def send_keys_to_active_element(key)
177
+ text = ::Selenium::WebDriver::Keys.encode(key).join('')
178
+ execute :send_keys_to_active_element, {}, { value: text.split(//) }
179
+ end
180
+
181
+ # For Appium
182
+ # override
183
+ def page_source
184
+ # For W3C
185
+ # execute_script('var source = document.documentElement.outerHTML;' \
186
+ # 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
187
+ # 'return source;')
188
+ execute :get_page_source
189
+ end
164
190
 
165
- w3c_capabilities[name] = value
191
+ # For Appium
192
+ # override
193
+ def element_displayed?(element)
194
+ # For W3C
195
+ # https://github.com/SeleniumHQ/selenium/commit/b618499adcc3a9f667590652c5757c0caa703289
196
+ # execute_atom :isDisplayed, element
197
+ execute :is_element_displayed, id: element.ref
198
+ end
199
+
200
+ # For Appium
201
+ # override
202
+ def element_attribute(element, name)
203
+ # For W3C in Selenium Client
204
+ # execute_atom :getAttribute, element, name
205
+ execute :get_element_attribute, id: element.ref, name: name
206
+ end
207
+
208
+ # For Appium
209
+ # override
210
+ def active_element
211
+ ::Appium::Core::Element.new self, element_id_from(execute(:get_active_element))
212
+ end
213
+ alias switch_to_active_element active_element
214
+
215
+ # For Appium
216
+ # override
217
+ def find_element_by(how, what, parent = nil)
218
+ how, what = convert_locators(how, what)
219
+
220
+ id = if parent
221
+ execute :find_child_element, { id: parent }, { using: how, value: what }
222
+ else
223
+ execute :find_element, {}, { using: how, value: what }
224
+ end
225
+ ::Appium::Core::Element.new self, element_id_from(id)
226
+ end
227
+
228
+ # For Appium
229
+ # override
230
+ def find_elements_by(how, what, parent = nil)
231
+ how, what = convert_locators(how, what)
232
+
233
+ ids = if parent
234
+ execute :find_child_elements, { id: parent }, { using: how, value: what }
235
+ else
236
+ execute :find_elements, {}, { using: how, value: what }
237
+ end
238
+
239
+ ids.map { |id| ::Appium::Core::Element.new self, element_id_from(id) }
240
+ end
241
+
242
+ # For Appium
243
+ # @param [Hash] id The id which can get as a response from server
244
+ # @return [::Appium::Core::Element]
245
+ def convert_to_element(id)
246
+ ::Appium::Core::Element.new self, element_id_from(id)
247
+ end
248
+
249
+ # For Appium
250
+ # override
251
+ # called in 'extend DriverExtensions::HasNetworkConnection'
252
+ def network_connection
253
+ execute :get_network_connection
254
+ end
255
+
256
+ # For Appium
257
+ # override
258
+ # called in 'extend DriverExtensions::HasNetworkConnection'
259
+ def network_connection=(type)
260
+ execute :set_network_connection, {}, { parameters: { type: type } }
261
+ end
262
+
263
+ # For Appium
264
+ # No implementation for W3C webdriver module
265
+ # called in 'extend DriverExtensions::HasLocation'
266
+ def location
267
+ obj = execute(:get_location) || {}
268
+ ::Selenium::WebDriver::Location.new obj['latitude'], obj['longitude'], obj['altitude']
269
+ end
270
+
271
+ # For Appium
272
+ # No implementation for W3C webdriver module
273
+ def set_location(lat, lon, alt = 0.0, speed: nil)
274
+ loc = { latitude: lat, longitude: lon, altitude: alt }
275
+ loc[:speed] = speed unless speed.nil?
276
+ execute :set_location, {}, { location: loc }
277
+ end
278
+
279
+ #
280
+ # logs
281
+ #
282
+ # For Appium
283
+ # No implementation for W3C webdriver module
284
+ def available_log_types
285
+ types = execute :get_available_log_types
286
+ Array(types).map(&:to_sym)
287
+ end
288
+
289
+ # For Appium
290
+ # No implementation for W3C webdriver module
291
+ def log(type)
292
+ data = execute :get_log, {}, { type: type.to_s }
293
+
294
+ Array(data).map do |l|
295
+ begin
296
+ ::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
297
+ rescue KeyError
298
+ next
299
+ end
166
300
  end
301
+ end
167
302
 
168
- w3c_capabilities
303
+ # For Appium
304
+ def log_event(vendor, event)
305
+ execute :post_log_event, {}, { vendor: vendor, event: event }
169
306
  end
170
307
 
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?
308
+ # For Appium
309
+ def log_events(type = nil)
310
+ args = {}
311
+ args['type'] = type unless type.nil?
174
312
 
175
- ::Appium::Logger.warn "'forceMjsonwp' no longer works. Sending both W3C and MJSONWP capabilities" if force_mjsonwp
313
+ execute :get_log_events, {}, args
314
+ end
315
+
316
+ def take_viewport_screenshot
317
+ execute_script('mobile: viewportScreenshot')
318
+ end
176
319
 
177
- new_caps = add_appium_prefix(desired_capabilities)
178
- w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.from_oss(new_caps)
320
+ def take_element_screenshot(element)
321
+ execute :take_element_screenshot, id: element.ref
322
+ end
323
+
324
+ private
325
+
326
+ def unwrap_script_result(arg)
327
+ case arg
328
+ when Array
329
+ arg.map { |e| unwrap_script_result(e) }
330
+ when Hash
331
+ element_id = element_id_from(arg)
332
+ return ::Appium::Core::Element.new(self, element_id) if element_id
333
+
334
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
335
+ else
336
+ arg
337
+ end
338
+ end
339
+
340
+ def element_id_from(id)
341
+ id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
342
+ end
179
343
 
180
- {
181
- desiredCapabilities: desired_capabilities,
182
- capabilities: {
183
- firstMatch: [w3c_capabilities]
184
- }
185
- }
344
+ # Don't convert locators for Appium Client
345
+ # TODO: Only for Appium. Ideally, we'd like to keep the selenium-webdriver
346
+ def convert_locators(how, what)
347
+ # case how
348
+ # when 'class name'
349
+ # how = 'css selector'
350
+ # what = ".#{escape_css(what)}"
351
+ # when 'id'
352
+ # how = 'css selector'
353
+ # what = "##{escape_css(what)}"
354
+ # when 'name'
355
+ # how = 'css selector'
356
+ # what = "*[name='#{escape_css(what)}']"
357
+ # when 'tag name'
358
+ # how = 'css selector'
359
+ # end
360
+ [how, what]
186
361
  end
187
362
  end # class Bridge
188
363
  end # class Base
@@ -19,10 +19,10 @@ 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
+ ::Selenium::WebDriver::Remote::Capabilities.new(opts_caps)
26
26
  end
27
27
  end
28
28
  end