Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions appium/webdriver/command_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import enum


class CommandMethod(enum.Enum):
GET = 'GET'
HEAD = 'HEAD'
POST = 'POST'
PUT = 'PUT'
DELETE = 'DELETE'
CONNECT = 'CONNECT'
OPTIONS = 'OPTIONS'
TRACE = 'TRACE'
PATCH = 'PATCH'
91 changes: 91 additions & 0 deletions appium/webdriver/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from selenium.webdriver.remote.remote_connection import RemoteConnection

from appium.common.logger import logger
from appium.webdriver.command_method import CommandMethod
from appium.webdriver.common.mobileby import MobileBy

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

return MobileSwitchTo(self)

def add_command(self, method: CommandMethod, url: str, name: str) -> T:
"""Add a custom command as 'name'

Args:
method: The method of HTTP request. Available methods are CommandMethod.
url: The url is URL template as https://www.w3.org/TR/webdriver/#endpoints.
`$sessionId` is a placeholder of current session id.
Other placeholders can be specified with `$` prefix like `$id`.
Then, `{'id': 'some id'}` argument in `execute_custom_command` replaces
the `$id` with the value, `some id`, in the request.
name: The name of a command to call in `execute_custom_command`.

Returns:
`appium.webdriver.webdriver.WebDriver`: Self instance

Raises:
ValueError

Examples:
Define 'test_command' as GET and 'session/$sessionId/path/to/custom/url'

>>> from appium.webdriver.command_method import CommandMethod
>>> driver.add_command(
method=CommandMethod.GET,
url='session/$sessionId/path/to/custom/url',
name='test_command'
)

"""
if name in self.command_executor._commands:
raise ValueError("{} is already defined".format(name))

if not isinstance(method, CommandMethod):
raise ValueError(
"'{}' is invalid. Valid method should be one of '{}'.".format(method, CommandMethod.__name__)
)

self.command_executor._commands[name] = (method.value, url)
return self

def execute_custom_command(self, name: str, args: Dict = {}) -> Any:
"""Execute a custom command defined as 'name' with args command.

Args:
name: The name of command defined by `add_command`.
args: The argument as this command body

Returns:
'value' JSON object field in the response body.

Raises:
ValueError, selenium.common.exceptions.WebDriverException

Examples:
Calls 'test_command' command without arguments.

>>> from appium.webdriver.command_method import CommandMethod
>>> driver.add_command(
method=CommandMethod.GET,
url='session/$sessionId/path/to/custom/url',
name='test_command'
)
>>> result = driver.execute_custom_command('test_command')

Calls 'test_command' command with arguments.

>>> from appium.webdriver.command_method import CommandMethod
>>> driver.add_command(
method=CommandMethod.POST,
url='session/$sessionId/path/to/custom/url',
name='test_command'
)
>>> result = driver.execute_custom_command('test_command', {'dummy': 'test argument'})

Calls 'test_command' command with arguments, and the path has 'element id'.
The '$id' will be 'element id' by 'id' key in the given argument.

>>> from appium.webdriver.command_method import CommandMethod
>>> driver.add_command(
method=CommandMethod.POST,
url='session/$sessionId/path/to/$id/url',
name='test_command'
)
>>> result = driver.execute_custom_command('test_command', {'id': 'element id', 'dummy': 'test argument'})

"""
if name not in self.command_executor._commands:
raise ValueError("No {} custom command. Please add it by 'add_command'".format(name))
return self.execute(name, args)['value']

# pylint: disable=protected-access

def _addCommands(self) -> None:
Expand Down
70 changes: 69 additions & 1 deletion test/unit/webdriver/webdriver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
import json

import httpretty
import pytest
from mock import patch

from appium import version as appium_version
from appium import webdriver
from appium.webdriver.command_method import CommandMethod
from appium.webdriver.webdriver import WebDriver
from test.unit.helper.test_helper import android_w3c_driver, appium_command, ios_w3c_driver
from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body, ios_w3c_driver


class TestWebDriverWebDriver(object):
Expand Down Expand Up @@ -250,6 +252,72 @@ def exceptionCallback(request, uri, headers):
mock_warning.assert_called_once()
assert events == {}

@httpretty.activate
def test_add_command(self):
driver = ios_w3c_driver()
httpretty.register_uri(
httpretty.GET,
appium_command('session/1234567890/path/to/custom/url'),
body=json.dumps({'value': {}}),
)
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
result = driver.execute_custom_command('test_command')

assert result == {}

@httpretty.activate
def test_add_command_body(self):
driver = ios_w3c_driver()
httpretty.register_uri(
httpretty.POST,
appium_command('session/1234567890/path/to/custom/url'),
body=json.dumps({'value': {}}),
)
driver.add_command(method=CommandMethod.POST, url='session/$sessionId/path/to/custom/url', name='test_command')
result = driver.execute_custom_command('test_command', {'dummy': 'test argument'})
assert result == {}

d = get_httpretty_request_body(httpretty.last_request())
assert d['dummy'] == 'test argument'

@httpretty.activate
def test_add_command_with_element_id(self):
driver = ios_w3c_driver()
httpretty.register_uri(
httpretty.GET,
appium_command('session/1234567890/path/to/custom/element_id/url'),
body=json.dumps({'value': {}}),
)
driver.add_command(
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/$id/url', name='test_command'
)
result = driver.execute_custom_command('test_command', {'id': 'element_id'})
assert result == {}

@httpretty.activate
def test_add_command_already_defined(self):
driver = ios_w3c_driver()
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
with pytest.raises(ValueError):
driver.add_command(
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command'
)

@httpretty.activate
def test_execute_custom_command(self):
driver = ios_w3c_driver()
driver.add_command(method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command')
with pytest.raises(ValueError):
driver.add_command(
method=CommandMethod.GET, url='session/$sessionId/path/to/custom/url', name='test_command'
)

@httpretty.activate
def test_invalid_method(self):
driver = ios_w3c_driver()
with pytest.raises(ValueError):
driver.add_command(method='error', url='session/$sessionId/path/to/custom/url', name='test_command')


class SubWebDriver(WebDriver):
def __init__(self, command_executor, desired_capabilities, direct_connection=False):
Expand Down