appium_lib_core 4.5.0 → 5.0.0.beta4

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/unittest.yml +2 -2
  3. data/CHANGELOG.md +12 -5
  4. data/README.md +2 -1
  5. data/appium_lib_core.gemspec +4 -4
  6. data/lib/appium_lib_core.rb +2 -5
  7. data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
  8. data/lib/appium_lib_core/common/base.rb +0 -3
  9. data/lib/appium_lib_core/common/base/bridge.rb +284 -89
  10. data/lib/appium_lib_core/common/base/capabilities.rb +3 -3
  11. data/lib/appium_lib_core/common/base/{command.rb → device_ime.rb} +32 -7
  12. data/lib/appium_lib_core/common/base/driver.rb +128 -70
  13. data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
  14. data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
  15. data/lib/appium_lib_core/common/base/rotable.rb +1 -1
  16. data/lib/appium_lib_core/common/base/search_context.rb +9 -4
  17. data/lib/appium_lib_core/common/command.rb +259 -4
  18. data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
  19. data/lib/appium_lib_core/common/error.rb +4 -1
  20. data/lib/appium_lib_core/common/log.rb +4 -1
  21. data/lib/appium_lib_core/device.rb +1 -5
  22. data/lib/appium_lib_core/driver.rb +3 -4
  23. data/lib/appium_lib_core/{patch.rb → element.rb} +2 -7
  24. data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
  25. data/lib/appium_lib_core/version.rb +2 -2
  26. data/release_notes.md +14 -0
  27. data/script/commands.rb +3 -37
  28. metadata +21 -30
  29. data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -91
  30. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -248
  31. data/lib/appium_lib_core/common/command/common.rb +0 -110
  32. data/lib/appium_lib_core/common/command/mjsonwp.rb +0 -28
  33. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
@@ -30,7 +30,7 @@ module Appium
30
30
  #
31
31
  def rotation=(orientation)
32
32
  unless ORIENTATIONS.include?(orientation)
33
- raise ArgumentError, "expected #{ORIENTATIONS.inspect}, got #{orientation.inspect}"
33
+ raise ::Appium::Core::Error::ArgumentError, "expected #{ORIENTATIONS.inspect}, got #{orientation.inspect}"
34
34
  end
35
35
 
36
36
  bridge.screen_orientation = orientation.to_s.upcase
@@ -159,7 +159,10 @@ module Appium
159
159
 
160
160
  def _set_by_from_finders(how)
161
161
  by = FINDERS[how.to_sym]
162
- raise ArgumentError, "cannot find element by #{how.inspect}. Available finders are #{FINDERS.keys}." unless by
162
+ unless by
163
+ raise ::Appium::Core::Error::ArgumentError,
164
+ "cannot find element by #{how.inspect}. Available finders are #{FINDERS.keys}."
165
+ end
163
166
 
164
167
  by
165
168
  end
@@ -171,16 +174,18 @@ module Appium
171
174
  when 1
172
175
  arg = args.first
173
176
 
174
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
177
+ unless arg.respond_to?(:shift)
178
+ raise ::Appium::Core::Error::ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
179
+ end
175
180
 
176
181
  # this will be a single-entry hash, so use #shift over #first or #[]
177
182
  arr = arg.dup.shift
178
183
 
179
- raise ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
184
+ raise ::Appium::Core::Error::ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
180
185
 
181
186
  arr
182
187
  else
183
- raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
188
+ raise ::Appium::Core::Error::ArgumentError, "wrong number of arguments (#{args.size} for 2)"
184
189
  end
185
190
  end
186
191
  end # module SearchContext
@@ -12,7 +12,262 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require_relative 'base/command'
16
- require_relative 'command/common'
17
- require_relative 'command/mjsonwp'
18
- require_relative 'command/w3c'
15
+ module Appium
16
+ module Core
17
+ # ref: https://github.com/appium/appium-base-driver/blob/master/lib/mjsonwp/routes.js
18
+ module Commands
19
+ # Some commands differ for each driver.
20
+ COMMAND = {
21
+ ###
22
+ # W3C
23
+ ###
24
+ status: [:get, 'status'],
25
+
26
+ #
27
+ # session handling
28
+ #
29
+
30
+ new_session: [:post, 'session'],
31
+ delete_session: [:delete, 'session/:session_id'],
32
+
33
+ #
34
+ # basic driver
35
+ #
36
+
37
+ get: [:post, 'session/:session_id/url'],
38
+ get_current_url: [:get, 'session/:session_id/url'],
39
+ back: [:post, 'session/:session_id/back'],
40
+ forward: [:post, 'session/:session_id/forward'],
41
+ refresh: [:post, 'session/:session_id/refresh'],
42
+ get_title: [:get, 'session/:session_id/title'],
43
+
44
+ #
45
+ # window and Frame handling
46
+ #
47
+
48
+ get_window_handle: [:get, 'session/:session_id/window'],
49
+ new_window: [:post, 'session/:session_id/window/new'],
50
+ close_window: [:delete, 'session/:session_id/window'],
51
+ switch_to_window: [:post, 'session/:session_id/window'],
52
+ get_window_handles: [:get, 'session/:session_id/window/handles'],
53
+ fullscreen_window: [:post, 'session/:session_id/window/fullscreen'],
54
+ minimize_window: [:post, 'session/:session_id/window/minimize'],
55
+ maximize_window: [:post, 'session/:session_id/window/maximize'],
56
+ set_window_size: [:post, 'session/:session_id/window/size'],
57
+ get_window_size: [:get, 'session/:session_id/window/size'],
58
+ set_window_position: [:post, 'session/:session_id/window/position'],
59
+ get_window_position: [:get, 'session/:session_id/window/position'],
60
+ set_window_rect: [:post, 'session/:session_id/window/rect'],
61
+ get_window_rect: [:get, 'session/:session_id/window/rect'],
62
+ switch_to_frame: [:post, 'session/:session_id/frame'],
63
+ switch_to_parent_frame: [:post, 'session/:session_id/frame/parent'],
64
+
65
+ #
66
+ # element
67
+ #
68
+
69
+ find_element: [:post, 'session/:session_id/element'],
70
+ find_elements: [:post, 'session/:session_id/elements'],
71
+ find_child_element: [:post, 'session/:session_id/element/:id/element'],
72
+ find_child_elements: [:post, 'session/:session_id/element/:id/elements'],
73
+ get_active_element: [:get, 'session/:session_id/element/active'],
74
+ is_element_selected: [:get, 'session/:session_id/element/:id/selected'],
75
+ get_element_attribute: [:get, 'session/:session_id/element/:id/attribute/:name'],
76
+ get_element_property: [:get, 'session/:session_id/element/:id/property/:name'],
77
+ get_element_css_value: [:get, 'session/:session_id/element/:id/css/:property_name'],
78
+ get_element_text: [:get, 'session/:session_id/element/:id/text'],
79
+ get_element_tag_name: [:get, 'session/:session_id/element/:id/name'],
80
+ get_element_rect: [:get, 'session/:session_id/element/:id/rect'],
81
+ is_element_enabled: [:get, 'session/:session_id/element/:id/enabled'],
82
+
83
+ #
84
+ # document handling
85
+ #
86
+
87
+ get_page_source: [:get, 'session/:session_id/source'],
88
+ execute_script: [:post, 'session/:session_id/execute/sync'],
89
+ execute_async_script: [:post, 'session/:session_id/execute/async'],
90
+
91
+ #
92
+ # cookies
93
+ #
94
+
95
+ get_all_cookies: [:get, 'session/:session_id/cookie'],
96
+ get_cookie: [:get, 'session/:session_id/cookie/:name'],
97
+ add_cookie: [:post, 'session/:session_id/cookie'],
98
+ delete_cookie: [:delete, 'session/:session_id/cookie/:name'],
99
+ delete_all_cookies: [:delete, 'session/:session_id/cookie'],
100
+
101
+ #
102
+ # timeouts
103
+ #
104
+
105
+ set_timeout: [:post, 'session/:session_id/timeouts'],
106
+
107
+ #
108
+ # actions
109
+ #
110
+
111
+ actions: [:post, 'session/:session_id/actions'],
112
+ release_actions: [:delete, 'session/:session_id/actions'],
113
+ print_page: [:post, 'session/:session_id/print'],
114
+
115
+ #
116
+ # Element Operations
117
+ #
118
+
119
+ element_click: [:post, 'session/:session_id/element/:id/click'],
120
+ element_tap: [:post, 'session/:session_id/element/:id/tap'],
121
+ element_clear: [:post, 'session/:session_id/element/:id/clear'],
122
+ element_send_keys: [:post, 'session/:session_id/element/:id/value'],
123
+
124
+ #
125
+ # alerts
126
+ #
127
+
128
+ dismiss_alert: [:post, 'session/:session_id/alert/dismiss'],
129
+ accept_alert: [:post, 'session/:session_id/alert/accept'],
130
+ get_alert_text: [:get, 'session/:session_id/alert/text'],
131
+ send_alert_text: [:post, 'session/:session_id/alert/text'],
132
+
133
+ #
134
+ # screenshot
135
+ #
136
+
137
+ take_screenshot: [:get, 'session/:session_id/screenshot'],
138
+ take_element_screenshot: [:get, 'session/:session_id/element/:id/screenshot'],
139
+
140
+ #
141
+ # server extensions
142
+ #
143
+
144
+ upload_file: [:post, 'session/:session_id/se/file'],
145
+
146
+ ###
147
+ # Used by Appium, but no in W3C
148
+ ###
149
+
150
+ # ::Appium::Core::Base::Commands::OSS has the following commands and Appium also use them.
151
+ # Delegated to ::Appium::Core::Base::Commands::OSS commands
152
+ is_element_displayed: [:get, 'session/:session_id/element/:id/displayed'], # hint: https://w3c.github.io/webdriver/#element-displayedness
153
+
154
+ get_timeouts: [:get, 'session/:session_id/timeouts'], # https://w3c.github.io/webdriver/#get-timeouts
155
+
156
+ # Add OSS commands to W3C commands. We can remove them if we would like to remove them from W3C module.
157
+ ### Session capability
158
+ get_capabilities: [:get, 'session/:session_id'],
159
+
160
+ ### rotatable
161
+ get_screen_orientation: [:get, 'session/:session_id/orientation'],
162
+ set_screen_orientation: [:post, 'session/:session_id/orientation'],
163
+
164
+ get_location: [:get, 'session/:session_id/location'],
165
+ set_location: [:post, 'session/:session_id/location'],
166
+
167
+ ### For IME
168
+ ime_get_available_engines: [:get, 'session/:session_id/ime/available_engines'],
169
+ ime_get_active_engine: [:get, 'session/:session_id/ime/active_engine'],
170
+ ime_is_activated: [:get, 'session/:session_id/ime/activated'],
171
+ ime_deactivate: [:post, 'session/:session_id/ime/deactivate'],
172
+ ime_activate_engine: [:post, 'session/:session_id/ime/activate'],
173
+
174
+ send_keys_to_active_element: [:post, 'session/:session_id/keys'],
175
+
176
+ ### Logs
177
+ get_available_log_types: [:get, 'session/:session_id/log/types'],
178
+ get_log: [:post, 'session/:session_id/log'],
179
+
180
+ ###
181
+ # Appium own
182
+ ###
183
+
184
+ # common
185
+ get_all_sessions: [:get, 'sessions'],
186
+ available_contexts: [:get, 'session/:session_id/contexts'],
187
+ set_context: [:post, 'session/:session_id/context'],
188
+ current_context: [:get, 'session/:session_id/context'],
189
+
190
+ touch_actions: [:post, 'session/:session_id/touch/perform'],
191
+ multi_touch: [:post, 'session/:session_id/touch/multi/perform'],
192
+
193
+ set_immediate_value: [:post, 'session/:session_id/appium/element/:id/value'],
194
+ replace_value: [:post, 'session/:session_id/appium/element/:id/replace_value'],
195
+
196
+ launch_app: [:post, 'session/:session_id/appium/app/launch'],
197
+ close_app: [:post, 'session/:session_id/appium/app/close'],
198
+ reset: [:post, 'session/:session_id/appium/app/reset'],
199
+ background_app: [:post, 'session/:session_id/appium/app/background'],
200
+ app_strings: [:post, 'session/:session_id/appium/app/strings'],
201
+
202
+ device_locked?: [:post, 'session/:session_id/appium/device/is_locked'],
203
+ unlock: [:post, 'session/:session_id/appium/device/unlock'],
204
+ lock: [:post, 'session/:session_id/appium/device/lock'],
205
+ device_time: [:get, 'session/:session_id/appium/device/system_time'],
206
+ install_app: [:post, 'session/:session_id/appium/device/install_app'],
207
+ remove_app: [:post, 'session/:session_id/appium/device/remove_app'],
208
+ app_installed?: [:post, 'session/:session_id/appium/device/app_installed'],
209
+ activate_app: [:post, 'session/:session_id/appium/device/activate_app'],
210
+ terminate_app: [:post, 'session/:session_id/appium/device/terminate_app'],
211
+ app_state: [:post, 'session/:session_id/appium/device/app_state'],
212
+ shake: [:post, 'session/:session_id/appium/device/shake'],
213
+ hide_keyboard: [:post, 'session/:session_id/appium/device/hide_keyboard'],
214
+ press_keycode: [:post, 'session/:session_id/appium/device/press_keycode'],
215
+ long_press_keycode: [:post, 'session/:session_id/appium/device/long_press_keycode'],
216
+ # keyevent is only for Selendroid
217
+ keyevent: [:post, 'session/:session_id/appium/device/keyevent'],
218
+ push_file: [:post, 'session/:session_id/appium/device/push_file'],
219
+ pull_file: [:post, 'session/:session_id/appium/device/pull_file'],
220
+ pull_folder: [:post, 'session/:session_id/appium/device/pull_folder'],
221
+ get_clipboard: [:post, 'session/:session_id/appium/device/get_clipboard'],
222
+ set_clipboard: [:post, 'session/:session_id/appium/device/set_clipboard'],
223
+ finger_print: [:post, 'session/:session_id/appium/device/finger_print'],
224
+ get_settings: [:get, 'session/:session_id/appium/settings'],
225
+ update_settings: [:post, 'session/:session_id/appium/settings'],
226
+ stop_recording_screen: [:post, 'session/:session_id/appium/stop_recording_screen'],
227
+ start_recording_screen: [:post, 'session/:session_id/appium/start_recording_screen'],
228
+ compare_images: [:post, 'session/:session_id/appium/compare_images'],
229
+ is_keyboard_shown: [:get, 'session/:session_id/appium/device/is_keyboard_shown'],
230
+ execute_driver: [:post, 'session/:session_id/appium/execute_driver'],
231
+ post_log_event: [:post, 'session/:session_id/appium/log_event'],
232
+ get_log_events: [:post, 'session/:session_id/appium/events']
233
+ }.freeze
234
+
235
+ COMMAND_ANDROID = {
236
+ open_notifications: [:post, 'session/:session_id/appium/device/open_notifications'],
237
+ toggle_airplane_mode: [:post, 'session/:session_id/appium/device/toggle_airplane_mode'],
238
+ start_activity: [:post, 'session/:session_id/appium/device/start_activity'],
239
+ current_activity: [:get, 'session/:session_id/appium/device/current_activity'],
240
+ current_package: [:get, 'session/:session_id/appium/device/current_package'],
241
+ get_system_bars: [:get, 'session/:session_id/appium/device/system_bars'],
242
+ get_display_density: [:get, 'session/:session_id/appium/device/display_density'],
243
+ toggle_wifi: [:post, 'session/:session_id/appium/device/toggle_wifi'],
244
+ toggle_data: [:post, 'session/:session_id/appium/device/toggle_data'],
245
+ toggle_location_services: [:post, 'session/:session_id/appium/device/toggle_location_services'],
246
+ end_coverage: [:post, 'session/:session_id/appium/app/end_test_coverage'],
247
+ get_performance_data_types: [:post, 'session/:session_id/appium/performanceData/types'],
248
+ get_performance_data: [:post, 'session/:session_id/appium/getPerformanceData'],
249
+ get_network_connection: [:get, 'session/:session_id/network_connection'], # defined also in OSS
250
+ set_network_connection: [:post, 'session/:session_id/network_connection'], # defined also in OSS
251
+
252
+ # only emulator
253
+ send_sms: [:post, 'session/:session_id/appium/device/send_sms'],
254
+ gsm_call: [:post, 'session/:session_id/appium/device/gsm_call'],
255
+ gsm_signal: [:post, 'session/:session_id/appium/device/gsm_signal'],
256
+ gsm_voice: [:post, 'session/:session_id/appium/device/gsm_voice'],
257
+ set_network_speed: [:post, 'session/:session_id/appium/device/network_speed'],
258
+ set_power_capacity: [:post, 'session/:session_id/appium/device/power_capacity'],
259
+ set_power_ac: [:post, 'session/:session_id/appium/device/power_ac'],
260
+
261
+ # For chromium: https://chromium.googlesource.com/chromium/src/+/master/chrome/test/chromedriver/server/http_handler.cc
262
+ chrome_send_command: [:post, 'session/:session_id/goog/cdp/execute']
263
+ }.freeze
264
+
265
+ COMMAND_IOS = {
266
+ touch_id: [:post, 'session/:session_id/appium/simulator/touch_id'],
267
+ toggle_touch_id_enrollment: [:post, 'session/:session_id/appium/simulator/toggle_touch_id_enrollment']
268
+ }.freeze
269
+
270
+ COMMANDS = {}.merge(COMMAND).merge(COMMAND_ANDROID).merge(COMMAND_IOS).freeze
271
+ end # module Commands
272
+ end # module Core
273
+ end # module Appium
@@ -25,8 +25,8 @@ module Appium
25
25
  end
26
26
 
27
27
  def press_keycode(key, metastate: [], flags: [])
28
- raise ArgumentError, 'flags should be Array' unless flags.is_a? Array
29
- raise ArgumentError, 'metastates should be Array' unless metastate.is_a? Array
28
+ raise ::Appium::Core::Error::ArgumentError, 'flags should be Array' unless flags.is_a? Array
29
+ raise ::Appium::Core::Error::ArgumentError, 'metastates should be Array' unless metastate.is_a? Array
30
30
 
31
31
  args = { keycode: key }
32
32
  args[:metastate] = metastate.reduce(0) { |acc, meta| acc | meta } unless metastate.empty?
@@ -36,8 +36,8 @@ module Appium
36
36
  end
37
37
 
38
38
  def long_press_keycode(key, metastate: [], flags: [])
39
- raise ArgumentError, 'flags should be Array' unless flags.is_a? Array
40
- raise ArgumentError, 'metastates should be Array' unless metastate.is_a? Array
39
+ raise ::Appium::Core::Error::ArgumentError, 'flags should be Array' unless flags.is_a? Array
40
+ raise ::Appium::Core::Error::ArgumentError, 'metastates should be Array' unless metastate.is_a? Array
41
41
 
42
42
  args = { keycode: key }
43
43
  args[:metastate] = metastate.reduce(0) { |acc, meta| acc | meta } unless metastate.empty?
@@ -27,8 +27,11 @@ module Appium
27
27
 
28
28
  class UnsupportedOperationError < CoreError; end
29
29
 
30
- # Server side error
30
+ # Server side errors
31
31
  class ServerError < CoreError; end
32
+
33
+ # ruby_lib_core library specific errors
34
+ class ArgumentError < CoreError; end
32
35
  end
33
36
  end
34
37
  end
@@ -65,7 +65,10 @@ module Appium
65
65
  end
66
66
 
67
67
  def event=(log_event)
68
- raise ArgumentError('log_event should be Hash like { vendor: "appium", event: "funEvent"}') unless log_event.is_a?(Hash)
68
+ unless log_event.is_a?(Hash)
69
+ raise ::Appium::Core::Error::ArgumentError,
70
+ 'log_event should be Hash like { vendor: "appium", event: "funEvent"}'
71
+ end
69
72
 
70
73
  event vendor: log_event[:vendor], event: log_event[:event]
71
74
  end
@@ -79,11 +79,7 @@ module Appium
79
79
  end
80
80
 
81
81
  def create_bridge_command(method, &block)
82
- ::Appium::Core::Base::Bridge::MJSONWP.class_eval do
83
- undef_method method if method_defined? method
84
- block_given? ? class_eval(&block) : define_method(method) { execute method }
85
- end
86
- ::Appium::Core::Base::Bridge::W3C.class_eval do
82
+ ::Appium::Core::Base::Bridge.class_eval do
87
83
  undef_method method if method_defined? method
88
84
  block_given? ? class_eval(&block) : define_method(method) { execute method }
89
85
  end
@@ -367,11 +367,10 @@ module Appium
367
367
  end
368
368
 
369
369
  begin
370
- # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
371
- @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
370
+ @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
371
+ http_client: @http_client,
372
372
  desired_capabilities: @caps,
373
- url: @custom_url,
374
- listener: @listener)
373
+ url: @custom_url)
375
374
 
376
375
  if @direct_connect
377
376
  d_c = DirectConnections.new(@driver.capabilities)
@@ -12,16 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- # rubocop:disable Style/ClassAndModuleChildren
16
15
  module Appium
17
16
  module Core
18
17
  # Implement useful features for element.
19
18
  # Patch for Selenium Webdriver.
20
- class Selenium::WebDriver::Element
21
- # To extend Appium related SearchContext into ::Selenium::WebDriver::Element
19
+ class Element < ::Selenium::WebDriver::Element
22
20
  include ::Appium::Core::Base::SearchContext
23
-
24
- # TODO: Probably can remove own TakesScreenshot since Selenium 4
25
21
  include ::Appium::Core::Base::TakesScreenshot
26
22
 
27
23
  # Returns the value of attributes like below. Read each platform to know more details.
@@ -152,7 +148,6 @@ module Appium
152
148
  end
153
149
  File.open(png_path, 'wb') { |f| f << screenshot_as(:png) }
154
150
  end
155
- end
151
+ end # class Element
156
152
  end # module Core
157
153
  end # module Appium
158
- # rubocop:enable Style/ClassAndModuleChildren
@@ -21,7 +21,7 @@ module Appium
21
21
  # will trigger as soon as the file is required. in contrast a method
22
22
  # will trigger only when invoked.
23
23
  def self.patch_webdriver_element
24
- ::Selenium::WebDriver::Element.class_eval do
24
+ ::Appium::Core::Element.class_eval do
25
25
  # Cross platform way of entering text into a textfield
26
26
  def type(text, driver)
27
27
  driver.execute_script %(au.getElement('#{ref}').setValue('#{text}');)
@@ -14,7 +14,7 @@
14
14
 
15
15
  module Appium
16
16
  module Core
17
- VERSION = '4.5.0' unless defined? ::Appium::Core::VERSION
18
- DATE = '2021-03-14' unless defined? ::Appium::Core::DATE
17
+ VERSION = '5.0.0.beta4' unless defined? ::Appium::Core::VERSION
18
+ DATE = '2021-06-11' unless defined? ::Appium::Core::DATE
19
19
  end
20
20
  end
data/release_notes.md CHANGED
@@ -1,3 +1,17 @@
1
+ #### v4.6.0 2021-06-03
2
+
3
+ - [0dacfab](https://github.com/appium/ruby_lib_core/commit/0dacfab1256e1447e1f7a5974dfcf48ee0a72b9d) Release 4.6.0
4
+ - [b9f015d](https://github.com/appium/ruby_lib_core/commit/b9f015d7dea14964a0733f2385ebcff68da1e18e) feat: allow to add commands dynamically (#325)
5
+ - [3de96ee](https://github.com/appium/ruby_lib_core/commit/3de96eea133ccbcbc5c4d77adc7d67c065a5a38c) chore(deps-dev): update webmock requirement from ~> 3.12.1 to ~> 3.13.0 (#324)
6
+ - [f1a9e79](https://github.com/appium/ruby_lib_core/commit/f1a9e79f3bd4d134e125fc2ed9adcf3d085afc9a) docs: address func test code as working example
7
+ - [eb85b1b](https://github.com/appium/ruby_lib_core/commit/eb85b1b26623436cb0aae95a00fef7bc2d795520) remove ; in a test
8
+ - [1632637](https://github.com/appium/ruby_lib_core/commit/1632637fd872c0b80dfb97b8514ada6a7164eebf) chore(deps-dev): update rubocop requirement from = 1.11.0 to = 1.12.0 (#321)
9
+ - [b9e47aa](https://github.com/appium/ruby_lib_core/commit/b9e47aa9b02f060ffa91e8410ab97dc87d3640a4) docs: add docstring
10
+ - [954a2fe](https://github.com/appium/ruby_lib_core/commit/954a2feebb768a55b496a2614d9e4dd8b702fc1e) chore(deps-dev): update rubocop requirement from = 1.8.1 to = 1.11.0 (#316)
11
+ - [a5b9651](https://github.com/appium/ruby_lib_core/commit/a5b9651aa349c10bd9759fedac6f09e27012a5e5) chore(deps-dev): update webmock requirement from ~> 3.11.0 to ~> 3.12.1 (#319)
12
+ - [485c096](https://github.com/appium/ruby_lib_core/commit/485c096273178aa5e21f28d93545fd127cbb8735) test: add assertion
13
+
14
+
1
15
  #### v4.5.0 2021-03-14
2
16
 
3
17
  - [656230e](https://github.com/appium/ruby_lib_core/commit/656230e688ed86414c06efaa73bce7359933cc91) Release 4.5.0