Skip to content

Commit b4c15e2

Browse files
authored
feat: allow to add a command dynamically (#608)
* add add_commmand in python * add test * add exceptions, tweak method * append docstring * add $id example * use pytest.raises * add examples as docstring
1 parent 37258a3 commit b4c15e2

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

appium/webdriver/command_method.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import enum
16+
17+
18+
class CommandMethod(enum.Enum):
19+
GET = 'GET'
20+
HEAD = 'HEAD'
21+
POST = 'POST'
22+
PUT = 'PUT'
23+
DELETE = 'DELETE'
24+
CONNECT = 'CONNECT'
25+
OPTIONS = 'OPTIONS'
26+
TRACE = 'TRACE'
27+
PATCH = 'PATCH'

appium/webdriver/webdriver.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from selenium.webdriver.remote.remote_connection import RemoteConnection
2424

2525
from appium.common.logger import logger
26+
from appium.webdriver.command_method import CommandMethod
2627
from appium.webdriver.common.mobileby import MobileBy
2728

2829
from .appium_connection import AppiumConnection
@@ -356,6 +357,96 @@ def switch_to(self) -> MobileSwitchTo:
356357

357358
return MobileSwitchTo(self)
358359

360+
def add_command(self, method: CommandMethod, url: str, name: str) -> T:
361+
"""Add a custom command as 'name'
362+
363+
Args:
364+
method: The method of HTTP request. Available methods are CommandMethod.
365+
url: The url is URL template as https://www.w3.org/TR/webdriver/#endpoints.
366+
`$sessionId` is a placeholder of current session id.
367+
Other placeholders can be specified with `$` prefix like `$id`.
368+
Then, `{'id': 'some id'}` argument in `execute_custom_command` replaces
369+
the `$id` with the value, `some id`, in the request.
370+
name: The name of a command to call in `execute_custom_command`.
371+
372+
Returns:
373+
`appium.webdriver.webdriver.WebDriver`: Self instance
374+
375+
Raises:
376+
ValueError
377+
378+
Examples:
379+
Define 'test_command' as GET and 'session/$sessionId/path/to/custom/url'
380+
381+
>>> from appium.webdriver.command_method import CommandMethod
382+
>>> driver.add_command(
383+
method=CommandMethod.GET,
384+
url='session/$sessionId/path/to/custom/url',
385+
name='test_command'
386+
)
387+
388+
"""
389+
if name in self.command_executor._commands:
390+
raise ValueError("{} is already defined".format(name))
391+
392+
if not isinstance(method, CommandMethod):
393+
raise ValueError(
394+
"'{}' is invalid. Valid method should be one of '{}'.".format(method, CommandMethod.__name__)
395+
)
396+
397+
self.command_executor._commands[name] = (method.value, url)
398+
return self
399+
400+
def execute_custom_command(self, name: str, args: Dict = {}) -> Any:
401+
"""Execute a custom command defined as 'name' with args command.
402+
403+
Args:
404+
name: The name of command defined by `add_command`.
405+
args: The argument as this command body
406+
407+
Returns:
408+
'value' JSON object field in the response body.
409+
410+
Raises:
411+
ValueError, selenium.common.exceptions.WebDriverException
412+
413+
Examples:
414+
Calls 'test_command' command without arguments.
415+
416+
>>> from appium.webdriver.command_method import CommandMethod
417+
>>> driver.add_command(
418+
method=CommandMethod.GET,
419+
url='session/$sessionId/path/to/custom/url',
420+
name='test_command'
421+
)
422+
>>> result = driver.execute_custom_command('test_command')
423+
424+
Calls 'test_command' command with arguments.
425+
426+
>>> from appium.webdriver.command_method import CommandMethod
427+
>>> driver.add_command(
428+
method=CommandMethod.POST,
429+
url='session/$sessionId/path/to/custom/url',
430+
name='test_command'
431+
)
432+
>>> result = driver.execute_custom_command('test_command', {'dummy': 'test argument'})
433+
434+
Calls 'test_command' command with arguments, and the path has 'element id'.
435+
The '$id' will be 'element id' by 'id' key in the given argument.
436+
437+
>>> from appium.webdriver.command_method import CommandMethod
438+
>>> driver.add_command(
439+
method=CommandMethod.POST,
440+
url='session/$sessionId/path/to/$id/url',
441+
name='test_command'
442+
)
443+
>>> result = driver.execute_custom_command('test_command', {'id': 'element id', 'dummy': 'test argument'})
444+
445+
"""
446+
if name not in self.command_executor._commands:
447+
raise ValueError("No {} custom command. Please add it by 'add_command'".format(name))
448+
return self.execute(name, args)['value']
449+
359450
# pylint: disable=protected-access
360451

361452
def _addCommands(self) -> None:

test/unit/webdriver/webdriver_test.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
import json
1616

1717
import httpretty
18+
import pytest
1819
from mock import patch
1920

2021
from appium import version as appium_version
2122
from appium import webdriver
23+
from appium.webdriver.command_method import CommandMethod
2224
from appium.webdriver.webdriver import WebDriver
23-
from test.unit.helper.test_helper import android_w3c_driver, appium_command, ios_w3c_driver
25+
from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body, ios_w3c_driver
2426

2527

2628
class TestWebDriverWebDriver(object):
@@ -250,6 +252,72 @@ def exceptionCallback(request, uri, headers):
250252
mock_warning.assert_called_once()
251253
assert events == {}
252254

255+
@httpretty.activate
256+
def test_add_command(self):
257+
driver = ios_w3c_driver()
258+
httpretty.register_uri(
259+
httpretty.GET,
260+
appium_command('session/1234567890/path/to/custom/url'),
261+
body=json.dumps({'value': {}}),
262+
)
263+
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
264+
result = driver.execute_custom_command('test_command')
265+
266+
assert result == {}
267+
268+
@httpretty.activate
269+
def test_add_command_body(self):
270+
driver = ios_w3c_driver()
271+
httpretty.register_uri(
272+
httpretty.POST,
273+
appium_command('session/1234567890/path/to/custom/url'),
274+
body=json.dumps({'value': {}}),
275+
)
276+
driver.add_command(method=CommandMethod.POST, url='session/$sessionId/path/to/custom/url', name='test_command')
277+
result = driver.execute_custom_command('test_command', {'dummy': 'test argument'})
278+
assert result == {}
279+
280+
d = get_httpretty_request_body(httpretty.last_request())
281+
assert d['dummy'] == 'test argument'
282+
283+
@httpretty.activate
284+
def test_add_command_with_element_id(self):
285+
driver = ios_w3c_driver()
286+
httpretty.register_uri(
287+
httpretty.GET,
288+
appium_command('session/1234567890/path/to/custom/element_id/url'),
289+
body=json.dumps({'value': {}}),
290+
)
291+
driver.add_command(
292+
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/$id/url', name='test_command'
293+
)
294+
result = driver.execute_custom_command('test_command', {'id': 'element_id'})
295+
assert result == {}
296+
297+
@httpretty.activate
298+
def test_add_command_already_defined(self):
299+
driver = ios_w3c_driver()
300+
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
301+
with pytest.raises(ValueError):
302+
driver.add_command(
303+
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command'
304+
)
305+
306+
@httpretty.activate
307+
def test_execute_custom_command(self):
308+
driver = ios_w3c_driver()
309+
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
310+
with pytest.raises(ValueError):
311+
driver.add_command(
312+
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command'
313+
)
314+
315+
@httpretty.activate
316+
def test_invalid_method(self):
317+
driver = ios_w3c_driver()
318+
with pytest.raises(ValueError):
319+
driver.add_command(method='error', url='session/$sessionId/path/to/custom/url', name='test_command')
320+
253321

254322
class SubWebDriver(WebDriver):
255323
def __init__(self, command_executor, desired_capabilities, direct_connection=False):

0 commit comments

Comments
 (0)