diff --git a/.travis.yml b/.travis.yml index c1d5ce33..79a9ff1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ language: python +dist: xenial + python: - "2.7" + - "3.4" + - "3.5" - "3.6" + - "3.7" install: - pip install -r ci-requirements.txt diff --git a/README.md b/README.md index 2f718b74..d7e05053 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,24 @@ download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz). ``` - You can customise `CHANGELOG.rst` with commit messages following [.gitchangelog.rc](.gitchangelog.rc) - It generates readable changelog +- `pip install -r development.txt` ## Run tests +### Unit + +``` +$ py.test test/unit +``` + +Run with `pytest-xdist` + +``` +$ py.test -n 2 test/unit +``` + +### Functional + ``` $ py.test test/functional/ios/find_by_ios_class_chain_tests.py ``` diff --git a/ci-requirements.txt b/ci-requirements.txt index f60a26a4..d983ccbd 100644 --- a/ci-requirements.txt +++ b/ci-requirements.txt @@ -3,3 +3,4 @@ astroid==1.6.5 isort==4.3.4 pylint==1.9.3 autopep8==1.4.3 +httpretty==0.9.6 diff --git a/ci.sh b/ci.sh index 2176f419..2e63a164 100755 --- a/ci.sh +++ b/ci.sh @@ -8,4 +8,4 @@ if [[ $result ]] ; then fi python -m pylint --rcfile .pylintrc appium test --py3k -python -m pytest test/unit/* +python -m pytest test/unit/ diff --git a/development.txt b/development.txt new file mode 100644 index 00000000..e04c98bb --- /dev/null +++ b/development.txt @@ -0,0 +1 @@ +-r ci-requirements.txt diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 00000000..cc173e9d --- /dev/null +++ b/test/unit/__init__.py @@ -0,0 +1,13 @@ +#!/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. diff --git a/test/unit/helper/__init__.py b/test/unit/helper/__init__.py new file mode 100644 index 00000000..cc173e9d --- /dev/null +++ b/test/unit/helper/__init__.py @@ -0,0 +1,13 @@ +#!/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. diff --git a/test/unit/helper/test_helper.py b/test/unit/helper/test_helper.py new file mode 100644 index 00000000..a8037442 --- /dev/null +++ b/test/unit/helper/test_helper.py @@ -0,0 +1,77 @@ +#!/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 unittest +import httpretty +import json + +from appium import webdriver + + +class TestHelper(): + + @staticmethod + def mock_android_driver(): + """ + Return a driver which is generated a mock response + + :return: An instance of WebDriver + :rtype: WebDriver + """ + + response_body_json = json.dumps( + { + 'value': { + 'sessionId': '1234567890', + 'capabilities': { + 'platform': 'LINUX', + 'desired': { + 'platformName': 'Android', + 'automationName': 'uiautomator2', + 'platformVersion': '7.1.1', + 'deviceName': 'Android Emulator', + 'app': '/test/apps/ApiDemos-debug.apk', + }, + 'platformName': 'Android', + 'automationName': 'uiautomator2', + 'platformVersion': '7.1.1', + 'deviceName': 'emulator-5554', + 'app': '/test/apps/ApiDemos-debug.apk', + 'deviceUDID': 'emulator-5554', + 'appPackage': 'com.example.android.apis', + 'appWaitPackage': 'com.example.android.apis', + 'appActivity': 'com.example.android.apis.ApiDemos', + 'appWaitActivity': 'com.example.android.apis.ApiDemos' + } + } + } + ) + + httpretty.register_uri( + httpretty.POST, + 'http://localhost:4723/wd/hub/session', + body=response_body_json + ) + + desired_caps = { + 'platformName': 'Android', + 'deviceName': 'Android Emulator', + 'app': 'path/to/app', + 'automationName': 'UIAutomator2' + } + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', + desired_caps + ) + return driver diff --git a/test/unit/webdriver/device/clipboard_test.py b/test/unit/webdriver/device/clipboard_test.py new file mode 100644 index 00000000..0c4656ad --- /dev/null +++ b/test/unit/webdriver/device/clipboard_test.py @@ -0,0 +1,35 @@ +#!/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 test.unit.helper.test_helper import TestHelper + +import json +import httpretty + + +class TestWebDriverDeviceClipboard(object): + + @httpretty.activate + def test_clipboard(self): + driver = TestHelper.mock_android_driver() + httpretty.register_uri( + httpretty.POST, + 'http://localhost:4723/wd/hub/session/1234567890/appium/device/set_clipboard', + body='{"value": ""}' + ) + driver.set_clipboard_text('hello') + + d = json.loads(httpretty.last_request().body.decode('utf-8')) + assert d['content'] == 'aGVsbG8=' + assert d['contentType'] == 'plaintext' diff --git a/test/unit/multi_action_tests.py b/test/unit/webdriver/multi_action_test.py similarity index 78% rename from test/unit/multi_action_tests.py rename to test/unit/webdriver/multi_action_test.py index 4e0133e4..ec8bd652 100644 --- a/test/unit/multi_action_tests.py +++ b/test/unit/webdriver/multi_action_test.py @@ -12,18 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - +import pytest from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction -class MultiActionTests(unittest.TestCase): - def setUp(self): - self._multi_action = MultiAction(DriverStub()) +class TestMultiAction(object): + @pytest.fixture + def multi_action(self): + return MultiAction(DriverStub()) - def test_json(self): - self.maxDiff = None + def test_json(self, multi_action): json = { 'actions': [ [ @@ -40,23 +39,19 @@ def test_json(self): } t1 = TouchAction(DriverStub()).press(ElementStub(1)).move_to(x=10, y=20).release() t2 = TouchAction(DriverStub()).press(ElementStub(5), 11, 30).move_to(x=12, y=-300).release() - self._multi_action.add(t1, t2) - self.assertEqual(json, self._multi_action.json_wire_gestures) + multi_action.add(t1, t2) + assert json == multi_action.json_wire_gestures class DriverStub(object): - def execute(self, action, params): + def execute(self, _action, _params): print("driver.execute called") class ElementStub(object): - def __init__(self, id): - self._id = id + def __init__(self, e_id): + self._id = e_id @property def id(self): return self._id - - -if __name__ == "__main__": - unittest.main() diff --git a/test/unit/touch_action_tests.py b/test/unit/webdriver/touch_action_test.py similarity index 62% rename from test/unit/touch_action_tests.py rename to test/unit/webdriver/touch_action_test.py index eb4468d4..0d752fd6 100644 --- a/test/unit/touch_action_tests.py +++ b/test/unit/webdriver/touch_action_test.py @@ -12,43 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +import pytest from appium.webdriver.common.touch_action import TouchAction -class TouchActionTests(unittest.TestCase): - def setUp(self): - self._touch_action = TouchAction(DriverStub()) +class TestTouchAction(object): - def test_tap_json(self): + @pytest.fixture + def touch_action(self): + return TouchAction(DriverStub()) + + def test_tap_json(self, touch_action): json = [ {'action': 'tap', 'options': {'count': 1, 'element': 1}} ] - self._touch_action.tap(ElementStub(1)) - self.assertEqual(json, self._touch_action.json_wire_gestures) + touch_action.tap(ElementStub(1)) + assert json == touch_action.json_wire_gestures - def test_tap_x_y_json(self): + def test_tap_x_y_json(self, touch_action): json = [ {'action': 'tap', 'options': {'x': 3, 'y': 4, 'count': 1, 'element': 2}} ] - self._touch_action.tap(ElementStub(2), 3, 4) - self.assertEqual(json, self._touch_action.json_wire_gestures) + touch_action.tap(ElementStub(2), 3, 4) + assert json == touch_action.json_wire_gestures class DriverStub(object): - def execute(self, action, params): + def execute(self, _action, _params): print("driver.execute called") class ElementStub(object): - def __init__(self, id, x=None, y=None, count=None): - self._id = id + def __init__(self, e_id, _x=None, _y=None, _count=None): + self._id = e_id @property def id(self): return self._id - - -if __name__ == "__main__": - unittest.main() diff --git a/test/unit/webdriver/webdriver_test.py b/test/unit/webdriver/webdriver_test.py new file mode 100644 index 00000000..9b293983 --- /dev/null +++ b/test/unit/webdriver/webdriver_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 json +import httpretty + +from appium import webdriver + + +class TestWebDriverWebDriver(object): + + @httpretty.activate + def test_create_session(self): + httpretty.register_uri( + httpretty.POST, + 'http://localhost:4723/wd/hub/session', + body='{ "value": { "sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}}}' + ) + + desired_caps = { + 'platformName': 'Android', + 'deviceName': 'Android Emulator', + 'app': 'path/to/app', + 'automationName': 'UIAutomator2' + } + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', + desired_caps + ) + + assert len(httpretty.HTTPretty.latest_requests) == 1 + + request = httpretty.HTTPretty.latest_requests[0] + assert request.headers['content-type'] == 'application/json;charset=UTF-8' + + request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) + assert request_json.get('capabilities') is not None + assert request_json.get('desiredCapabilities') is not None + + assert driver.session_id == 'session-id' + assert driver.w3c + + @httpretty.activate + def test_create_session_forceMjsonwp(self): + httpretty.register_uri( + httpretty.POST, + 'http://localhost:4723/wd/hub/session', + body='{ "capabilities": {"deviceName": "Android Emulator"}, "status": 0, "sessionId": "session-id"}' + ) + + desired_caps = { + 'platformName': 'Android', + 'deviceName': 'Android Emulator', + 'app': 'path/to/app', + 'automationName': 'UIAutomator2', + 'forceMjsonwp': True + } + driver = webdriver.Remote( + 'http://localhost:4723/wd/hub', + desired_caps + ) + + assert len(httpretty.HTTPretty.latest_requests) == 1 + + request = httpretty.HTTPretty.latest_requests[0] + assert request.headers['content-type'] == 'application/json;charset=UTF-8' + + request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8')) + assert request_json.get('capabilities') is None + assert request_json.get('desiredCapabilities') is not None + + assert driver.session_id == 'session-id' + assert driver.w3c is False