diff --git a/appium/webdriver/extensions/execute_driver.py b/appium/webdriver/extensions/execute_driver.py new file mode 100644 index 00000000..5c866a39 --- /dev/null +++ b/appium/webdriver/extensions/execute_driver.py @@ -0,0 +1,61 @@ +#!/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. + +from selenium import webdriver + +from ..mobilecommand import MobileCommand as Command + + +class ExecuteDriver(webdriver.Remote): + + def execute_driver(self, script, script_type='webdriverio', timeout_ms=None): + """Run a set of script against the current session, allowing execution of many commands in one Appium request. + Please read http://appium.io/docs/en/commands/session/execute-driver for more details about the acceptable + scripts and the output format. + + Args: + script (string): The string consisting of the script itself + script_type (string): The name of the script type. Defaults to 'webdriverio'. + timeout_ms (optional): The number of `ms` Appium should wait for the script to finish before killing it due to timeout_ms. + + Usage: + self.driver.execute_driver(script='return [];') + self.driver.execute_driver(script='return [];', script_type='webdriverio') + self.driver.execute_driver(script='return [];', script_type='webdriverio', timeout_ms=10000) + + Returns: + ExecuteDriver.Result: The result of the script. It has 'result' and 'logs' keys. + + Raises: + WebDriverException: If something error happenes in the script. The message has the original error message. + """ + + class Result(object): + + def __init__(self, response): + self.result = response['result'] + self.logs = response['logs'] + + option = {'script': script, 'type': script_type} + if timeout_ms is not None: + option['timeout'] = timeout_ms + + response = self.execute(Command.EXECUTE_DRIVER, option)['value'] + return Result(response) + + # pylint: disable=protected-access + + def _addCommands(self): + self.command_executor._commands[Command.EXECUTE_DRIVER] = \ + ('POST', '/session/$sessionId/appium/execute_driver') diff --git a/appium/webdriver/mobilecommand.py b/appium/webdriver/mobilecommand.py index 565a8f61..3128a089 100644 --- a/appium/webdriver/mobilecommand.py +++ b/appium/webdriver/mobilecommand.py @@ -72,6 +72,8 @@ class MobileCommand(object): COMPARE_IMAGES = 'compareImages' IS_KEYBOARD_SHOWN = 'isKeyboardShown' + EXECUTE_DRIVER = 'executeDriver' + # Android OPEN_NOTIFICATIONS = 'openNotifications' START_ACTIVITY = 'startActivity' diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index bf18249f..60289d0b 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -40,6 +40,7 @@ from .extensions.clipboard import Clipboard from .extensions.context import Context from .extensions.device_time import DeviceTime +from .extensions.execute_driver import ExecuteDriver from .extensions.hw_actions import HardwareActions from .extensions.images_comparison import ImagesComparison from .extensions.ime import IME @@ -116,6 +117,7 @@ class WebDriver( Context, DeviceTime, Display, + ExecuteDriver, Gsm, HardwareActions, ImagesComparison, @@ -580,7 +582,7 @@ def find_elements_by_custom(self, selector): """ return self.find_elements(by=MobileBy.CUSTOM, value=selector) - def create_web_element(self, element_id): + def create_web_element(self, element_id, w3c=False): """Creates a web element with the specified element_id. Overrides method in Selenium WebDriver in order to always give them @@ -588,11 +590,12 @@ def create_web_element(self, element_id): Args: element_id (int): The element id to create a web element + w3c (bool): Whether the element is W3C or MJSONWP Returns: `MobileWebElement` """ - return MobileWebElement(self, element_id) + return MobileWebElement(self, element_id, w3c) def press_button(self, button_name): """Sends a physical button name to the device to simulate the user pressing. diff --git a/test/functional/ios/execute_driver_tests.py b/test/functional/ios/execute_driver_tests.py new file mode 100644 index 00000000..1ebe338b --- /dev/null +++ b/test/functional/ios/execute_driver_tests.py @@ -0,0 +1,57 @@ +#!/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 textwrap +import unittest + +from appium import webdriver +from helper import desired_capabilities + + +class ExecuteDriverTests(unittest.TestCase): + def setUp(self): + desired_caps = desired_capabilities.get_desired_capabilities('UICatalog.app.zip') + self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + + def tearDown(self): + self.driver.quit() + + def test_batch(self): + script = """ + const status = await driver.status(); + console.warn('warning message'); + return status; + """ + + response = self.driver.execute_driver(script=textwrap.dedent(script)) + assert(response.result['build']) + assert(response.logs['warn'] == ['warning message']) + + def test_batch_combination_python_script(self): + script = """ + console.warn('warning message'); + const element = await driver.findElement('accessibility id', 'Buttons'); + const rect = await driver.getElementRect(element.ELEMENT); + return [element, rect]; + """ + + response = self.driver.execute_driver(script=textwrap.dedent(script)) + r = response.result[0].rect + + assert(r == response.result[1]) + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(ExecuteDriverTests) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/unit/webdriver/device/execute_driver_test.py b/test/unit/webdriver/device/execute_driver_test.py new file mode 100644 index 00000000..544c3fae --- /dev/null +++ b/test/unit/webdriver/device/execute_driver_test.py @@ -0,0 +1,84 @@ +#!/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 textwrap +from test.unit.helper.test_helper import ( + android_w3c_driver, + appium_command, + get_httpretty_request_body +) + +import httpretty + + +class TestWebDriverDeviceActivities(object): + + @httpretty.activate + def test_batch(self): + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/appium/execute_driver'), + body='{"value": {"result":[' + '{"element-6066-11e4-a52e-4f735466cecf":"39000000-0000-0000-D39A-000000000000",' + '"ELEMENT":"39000000-0000-0000-D39A-000000000000"},' + '{"y":237,"x":18,"width":67,"height":24}],"logs":{' + '"error":[],"warn":["warning message"],"log":[]}}}' + ) + + script = """ + console.warn('warning message'); + const element = await driver.findElement('accessibility id', 'Buttons'); + const rect = await driver.getElementRect(element.ELEMENT); + return [element, rect]; + """ + response = driver.execute_driver(script=textwrap.dedent(script)) + # Python client convert an element item as WebElement in the result + assert response.result[0].id == '39000000-0000-0000-D39A-000000000000' + assert response.result[1]['y'] == 237 + assert response.logs['warn'] == ['warning message'] + + d = get_httpretty_request_body(httpretty.last_request()) + assert d['script'] == textwrap.dedent(script) + assert d['type'] == 'webdriverio' + assert 'timeout' not in d + + @httpretty.activate + def test_batch_with_timeout(self): + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/appium/execute_driver'), + body='{"value": {"result":[' + '{"element-6066-11e4-a52e-4f735466cecf":"39000000-0000-0000-D39A-000000000000",' + '"ELEMENT":"39000000-0000-0000-D39A-000000000000"},' + '{"y":237,"x":18,"width":67,"height":24}],"logs":{' + '"error":[],"warn":["warning message"],"log":[]}}}' + ) + + script = """ + console.warn('warning message'); + const element = await driver.findElement('accessibility id', 'Buttons'); + const rect = await driver.getElementRect(element.ELEMENT); + return [element, rect]; + """ + response = driver.execute_driver(script=textwrap.dedent(script), timeout_ms=10000) + assert response.result[0].id == '39000000-0000-0000-D39A-000000000000' + assert response.result[1]['y'] == 237 + assert response.logs['error'] == [] + + d = get_httpretty_request_body(httpretty.last_request()) + assert d['script'] == textwrap.dedent(script) + assert d['type'] == 'webdriverio' + assert d['timeout'] == 10000