Skip to content

Commit 91cee6b

Browse files
aguspeKazuCocoa
andauthored
feat: use Selenium's Bridge.add_command for custom command registration (#671)
* first try at implementation * address review comments --------- Co-authored-by: Kazuaki Matsuo <fly.49.89.over@gmail.com>
1 parent 4ccec26 commit 91cee6b

4 files changed

Lines changed: 55 additions & 58 deletions

File tree

lib/appium_lib_core/common/base/bridge.rb

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ class Bridge < ::Selenium::WebDriver::Remote::Bridge
4747
# No 'browserName' means the session is native appium connection
4848
APPIUM_NATIVE_BROWSER_NAME = 'appium'
4949

50-
attr_reader :available_commands
51-
5250
def browser
5351
@browser ||= begin
5452
name = @capabilities&.browser_name
@@ -74,7 +72,6 @@ def browser
7472
# )
7573
#
7674
def attach_to(session_id, platform_name, automation_name)
77-
@available_commands = ::Appium::Core::Commands::COMMANDS.dup
7875
@session_id = session_id
7976

8077
# generate a dummy capabilities instance which only has the given platformName and automationName
@@ -109,8 +106,6 @@ def attach_to(session_id, platform_name, automation_name)
109106
# driver = core.start_driver
110107
#
111108
def create_session(capabilities)
112-
@available_commands = ::Appium::Core::Commands::COMMANDS.dup
113-
114109
always_match = add_appium_prefix(capabilities)
115110
response = execute(:new_session, {}, { capabilities: { alwaysMatch: always_match, firstMatch: [{}] } }) # steep:ignore
116111

@@ -162,29 +157,16 @@ def json_create(value)
162157

163158
public
164159

165-
# command for Appium 2.0.
166-
167-
# Example:
168-
# driver.add_command(name: :available_contexts, method: :get, url: 'session/:session_id/contexts') do
169-
# execute(:available_contexts, {}) || []
170-
# end
171-
# Then,
172-
# driver.available_contexts #=> ["NATIVE_APP"]
173-
174-
# def add_command(method:, url:, name:, &block)
175-
# Bridge.add_command name, method, url, &block
176-
# end
177-
178-
def add_command(method:, url:, name:, &block)
179-
::Appium::Logger.info "Overriding the method '#{name}' for '#{url}'" if @available_commands.key? name
180-
181-
@available_commands[name] = [method, url]
182-
183-
::Appium::Core::Device.add_endpoint_method name, &block
160+
# Override Selenium's command_list to use Appium's command definitions
161+
# instead of the default W3C COMMANDS from Selenium.
162+
def command_list
163+
::Appium::Core::Commands::COMMANDS
184164
end
185165

166+
# Override Selenium's commands to resolve extra_commands from this subclass
167+
# rather than the parent Selenium::WebDriver::Remote::Bridge.
186168
def commands(command)
187-
@available_commands[command] || Bridge.extra_commands[command]
169+
command_list[command] || self.class.extra_commands&.[](command)
188170
end
189171

190172
def status

lib/appium_lib_core/common/base/driver.rb

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,25 +135,21 @@ def update_sending_request_to(protocol:, host:, port:, path:)
135135
# drivers/plugins in Appium 2.0. Appium 2.0 and its custom drivers/plugins allow you
136136
# to define custom commands that are not part of W3C spec.
137137
#
138+
# Uses Selenium's +Bridge.add_command+ under the hood to register the command
139+
# and define the method on the bridge.
140+
#
138141
# @param [Symbol] method HTTP request method as https://www.w3.org/TR/webdriver/#endpoints
139142
# @param [string] url The url to URL template as https://www.w3.org/TR/webdriver/#endpoints.
140143
# +:session_id+ is the placeholder of 'session id'.
141144
# Other place holders can be specified with +:+ prefix like +:id+.
142145
# Then, the +:id+ will be replaced with a given value as the seconds argument of +execute+
143146
# @param [Symbol] name The name of method that is called as the driver instance method.
144-
# @param [Proc] block The block to involve as the method.
145-
# Please define a method that has the same +name+ with arguments you want.
146-
# The method must has +execute+ method. tHe +execute+ is calls the +url+
147-
# with the given parameters.
148-
# The first argument should be +name+ as symbol.
149-
# The second argument should be hash. If keys in the hash matches +:+ prefix
150-
# string in the given url, the matched string in the given url will be
151-
# values in the hash.
152-
# The third argument should be hash. The hash will be the request body.
153-
# Please read examples below for more details.
147+
# @param [Proc] block The block becomes the method body. It is executed in the context of
148+
# the bridge instance, so +execute+ is available. When no block is given,
149+
# a default implementation calling +execute(name)+ is used.
154150
# @raise [ArgumentError] If the given +method+ is invalid value.
155151
#
156-
# @example
152+
# @example Simple GET command (no block)
157153
#
158154
# @driver.add_command(
159155
# method: :get,
@@ -163,30 +159,37 @@ def update_sending_request_to(protocol:, host:, port:, path:)
163159
# # Send a GET request to 'session/<session id>/path/to/custom/url'
164160
# @driver.test_command
165161
#
162+
# @example POST command with arguments (do/end block)
166163
#
167164
# @driver.add_command(
168165
# method: :post,
169166
# url: 'session/:session_id/path/to/custom/url',
170167
# name: :test_command
171-
# ) do
172-
# def test_command(argument)
173-
# execute(:test_command, {}, { dummy: argument })
174-
# end
168+
# ) do |argument|
169+
# execute(:test_command, {}, { dummy: argument })
175170
# end
176171
# # Send a POST request to 'session/<session id>/path/to/custom/url'
177172
# # with body "{ dummy: 1 }" as JSON object. "1" is the argument.
178173
# # ':session_id' in the given 'url' is replaced with current 'session id'.
179174
# @driver.test_command(1)
180175
#
176+
# @example POST command with arguments (inline block)
177+
#
178+
# @driver.add_command(
179+
# method: :post,
180+
# url: 'session/:session_id/path/to/custom/url',
181+
# name: :test_command
182+
# ) { |argument| execute(:test_command, {}, { dummy: argument }) }
183+
# @driver.test_command(1)
184+
#
185+
# @example POST command with URL placeholders (do/end block)
181186
#
182187
# @driver.add_command(
183188
# method: :post,
184189
# url: 'session/:session_id/element/:id/custom/action',
185190
# name: :test_action_command
186-
# ) do
187-
# def test_action_command(element_id, action)
188-
# execute(:test_action_command, {id: element_id}, { dummy_action: action })
189-
# end
191+
# ) do |element_id, action|
192+
# execute(:test_action_command, {id: element_id}, { dummy_action: action })
190193
# end
191194
# # Send a POST request to 'session/<session id>/element/<element id>/custom/action'
192195
# # with body "{ dummy_action: #{action} }" as JSON object. "action" is the seconds argument.
@@ -195,10 +198,28 @@ def update_sending_request_to(protocol:, host:, port:, path:)
195198
# e = @driver.find_element :accessibility_id, 'an element'
196199
# @driver.test_action_command(e.id, 'action')
197200
#
201+
# @example POST command with URL placeholders (inline block)
202+
#
203+
# @driver.add_command(
204+
# method: :post,
205+
# url: 'session/:session_id/element/:id/custom/action',
206+
# name: :test_action_command
207+
# ) { |element_id, action| execute(:test_action_command, {id: element_id}, { dummy_action: action }) }
208+
# e = @driver.find_element :accessibility_id, 'an element'
209+
# @driver.test_action_command(e.id, 'action')
210+
#
198211
def add_command(method:, url:, name:, &block)
199212
raise ::Appium::Core::Error::ArgumentError, "Available method is either #{AVAILABLE_METHODS}" unless AVAILABLE_METHODS.include? method
200213

201-
@bridge.add_command method: method, url: url, name: name, &block
214+
::Appium::Logger.info "Overriding the command '#{name}' for '#{url}'" if Bridge.extra_commands&.key?(name)
215+
216+
# Use Selenium's Bridge.add_command to register the command and define the method.
217+
# When no block is given, create a default implementation that calls execute.
218+
block ||= proc { execute(name) }
219+
Bridge.add_command(name, method, url, &block)
220+
221+
# Ensure the driver delegates the new method to bridge
222+
self.class.class_eval { def_delegator :@bridge, name } unless self.class.method_defined?(name)
202223
end
203224

204225
### Methods for Appium

sig/lib/appium_lib_core/common/base/bridge.rbs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ module Appium
1010

1111
@browser: untyped
1212

13-
@available_commands: untyped
14-
1513
@session_id: untyped
1614

1715
# generate a dummy capabilities instance which only has the given platformName and automationName
@@ -51,8 +49,6 @@ module Appium
5149
# No 'browserName' means the session is native appium connection
5250
APPIUM_NATIVE_BROWSER_NAME: "appium"
5351

54-
attr_reader available_commands: untyped
55-
5652
def browser: () -> untyped
5753

5854
# Appium only.
@@ -115,8 +111,10 @@ module Appium
115111

116112
public
117113

118-
def add_command: (method: untyped, url: untyped, name: untyped) { (?) -> untyped } -> untyped
114+
# Override Selenium's command_list to use Appium's command definitions
115+
def command_list: () -> Hash[Symbol, Array[Symbol | String]]
119116

117+
# Override Selenium's commands to resolve extra_commands from this subclass
120118
def commands: (untyped command) -> untyped
121119

122120
def status: () -> untyped

test/unit/android/webdriver/w3c/commands_test.rb

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,8 @@ def test_add_command_block
5050
method: :post,
5151
url: 'session/:session_id/path/to/custom/url',
5252
name: :test_command
53-
) do
54-
def test_command(argument)
55-
execute(:test_command, {}, { dummy: argument })
56-
end
53+
) do |argument|
54+
execute(:test_command, {}, { dummy: argument })
5755
end
5856

5957
assert_equal @driver.respond_to?(:test_command), true
@@ -72,10 +70,8 @@ def test_add_command_block_element_id
7270
method: :post,
7371
url: 'session/:session_id/path/to/custom/:element_id/url',
7472
name: :test_command
75-
) do
76-
def test_command(argument)
77-
execute(:test_command, { element_id: 'dummy_element_id' }, { dummy: argument })
78-
end
73+
) do |argument|
74+
execute(:test_command, { element_id: 'dummy_element_id' }, { dummy: argument })
7975
end
8076

8177
assert_equal @driver.respond_to?(:test_command), true

0 commit comments

Comments
 (0)