Update python sample code
MODIFY
.gitignore
.gitignore
@@ -47,6 +47,7 @@ sample-code/php/composer.lock
sample-code/java/out/
sample-code/ruby/Gemfile.lock
sample-code/python/test/results/
+sample-code/python/test/input
sample-code/python/.pytest_cache/
sample-code/csharp/test/bin
sample-code/csharp/test/obj

MODIFY
.travis.yml
.travis.yml
@@ -129,3 +129,21 @@ jobs:
- npm install
script:
- npm run test
+
+ - stage:
+ name: Python
+ # only want to run this on the main master branch
+ if: branch = master AND type != pull_request
+ os: linux
+ language: python
+ python: "2.7"
+ env:
+ - SAUCE_LABS=1
+ before_install:
+ - pip install --upgrade pip
+ install:
+ - cd sample-code/python
+ - pip install -r requirements.txt
+ - pip install pytest-xdist
+ script:
+ - py.test -v -n 30 test

MODIFY
sample-code/javascript-wd/test/helpers/config.js
sample-code/javascript-wd/test/helpers/config.js
@@ -9,7 +9,7 @@ const SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY;
const sauceCaps = SAUCE_TESTING
? {
name: undefined, // will be added in tests
- build: `JavaScript (wd) Sample Code ${new Date()}`,
+ build: `JavaScript (wd) Sample Code ${process.env.TRAVIS_BUILD_ID ? process.env.TRAVIS_BUILD_ID : new Date()}`,
tags: ['e2e', 'appium', 'sample-code'],
}
: {};

MODIFY
sample-code/python/README.md
sample-code/python/README.md
@@ -12,6 +12,14 @@ pip install -r requirements.txt
py.test test
```
+To run them on Sauce Labs, make sure that you have the following environment
+variables set:
+```
+SAUCE_LABS
+SAUCE_USERNAME
+SAUCE_ACCESS_KEY
+```
+
### Run an arbitrary file
```

MODIFY
sample-code/python/requirements.txt
sample-code/python/requirements.txt
Appium-Python-Client>=0.28
pytest
+SauceClient

MODIFY
sample-code/python/test/conftest.py
sample-code/python/test/conftest.py
@@ -6,10 +6,9 @@ from helpers import ensure_dir
def pytest_configure(config):
- if not hasattr(config, 'slaveinput'):
+ if not hasattr(config, 'input'):
current_day = '{:%Y_%m_%d_%H_%S}'.format(datetime.datetime.now())
- ensure_dir('results')
- ensure_dir(os.path.join('slaveinput', current_day))
+ ensure_dir(os.path.join(os.path.dirname(__file__), 'input', current_day))
result_dir = os.path.join(os.path.dirname(__file__), 'results', current_day)
ensure_dir(result_dir)
result_dir_test_run = result_dir

MODIFY
sample-code/python/test/helpers.py
sample-code/python/test/helpers.py
import os
+import sys
from selenium.common.exceptions import InvalidSessionIdException
+from datetime import datetime
+from sauceclient import SauceClient
-ANDROID_APP_PATH = 'http://appium.github.io/appium/assets/ApiDemos-debug.apk' if os.getenv(
- 'SAUCE_LABS') else os.path.abspath('../apps/ApiDemos-debug.apk')
+ANDROID_BASE_CAPS = {
+ 'app': os.path.abspath('../apps/ApiDemos-debug.apk'),
+ 'automationName': 'UIAutomator2',
+ 'platformName': 'Android',
+ 'platformVersion': os.getenv('ANDROID_PLATFORM_VERSION') or '8.0',
+ 'deviceName': os.getenv('ANDROID_DEVICE_VERSION') or 'Android Emulator',
+}
-IOS_APP_PATH = 'http://appium.github.io/appium/assets/TestApp7.1.app.zip' if os.getenv(
- 'SAUCE_LABS') else os.path.abspath('../apps/TestApp.app.zip')
+IOS_BASE_CAPS = {
+ 'app': os.path.abspath('../apps/TestApp.app.zip'),
+ 'automationName': 'xcuitest',
+ 'platformName': 'iOS',
+ 'platformVersion': os.getenv('IOS_PLATFORM_VERSION') or '12.2',
+ 'deviceName': os.getenv('IOS_DEVICE_NAME') or 'iPhone 8 Simulator',
+ # 'showIOSLog': False,
+}
+
+if os.getenv('SAUCE_LABS') and os.getenv('SAUCE_USERNAME') and os.getenv('SAUCE_ACCESS_KEY'):
+ build_id = os.getenv('TRAVIS_BUILD_ID') or datetime.now().strftime('%B %d, %Y %H:%M:%S')
+ build_name = 'Python Sample Code %s' % build_id
+
+ ANDROID_BASE_CAPS['build'] = build_name
+ ANDROID_BASE_CAPS['tags'] = ['e2e', 'appium', 'sample-code', 'android', 'python']
+ ANDROID_BASE_CAPS['app'] = 'http://appium.github.io/appium/assets/ApiDemos-debug.apk'
+
+ IOS_BASE_CAPS['build'] = build_name
+ IOS_BASE_CAPS['tags'] = ['e2e', 'appium', 'sample-code', 'ios', 'python']
+ IOS_BASE_CAPS['app'] = 'http://appium.github.io/appium/assets/TestApp9.4.app.zip'
-if os.getenv('SAUCE_USERNAME') and os.getenv('SAUCE_ACCESS_KEY'):
EXECUTOR = 'http://{}:{}@ondemand.saucelabs.com:80/wd/hub'.format(
os.getenv('SAUCE_USERNAME'), os.getenv('SAUCE_ACCESS_KEY'))
+
+ sauce = SauceClient(os.getenv('SAUCE_USERNAME'), os.getenv('SAUCE_ACCESS_KEY'))
else:
EXECUTOR = 'http://127.0.0.1:4723/wd/hub'
@@ -20,11 +47,11 @@ def ensure_dir(directory):
os.makedirs(directory)
-def take_screenhot_and_logcat(driver, device_logger, calling_request):
+def take_screenshot_and_logcat(driver, device_logger, calling_request):
__save_log_type(driver, device_logger, calling_request, 'logcat')
-def take_screenhot_and_syslog(driver, device_logger, calling_request):
+def take_screenshot_and_syslog(driver, device_logger, calling_request):
__save_log_type(driver, device_logger, calling_request, 'syslog')
@@ -38,7 +65,12 @@ def __save_log_type(driver, device_logger, calling_request, type):
except InvalidSessionIdException:
logcat_data = ''
- with open(os.path.join(logcat_dir, '{}_{}.log'.format(calling_request, type)), 'wb') as logcat_file:
+ with open(os.path.join(logcat_dir, '{}_{}.log'.format(calling_request, type)), 'w') as logcat_file:
for data in logcat_data:
- data_string = '{}: {}'.format(data['timestamp'], data['message'])
- logcat_file.write((data_string + '\n').encode('UTF-8'))
+ data_string = '%s: %s\n' % (data['timestamp'], data['message'].encode('utf-8'))
+ logcat_file.write(data_string)
+
+def report_to_sauce(session_id):
+ print("Link to your job: https://saucelabs.com/jobs/%s" % session_id)
+ passed = str(sys.exc_info() == (None, None, None))
+ sauce.jobs.update_job(session_id, passed=passed)

MODIFY
sample-code/python/test/test_android_basic_interactions.py
sample-code/python/test/test_android_basic_interactions.py
import pytest
import os
import textwrap
+import copy
from appium import webdriver
-from helpers import take_screenhot_and_logcat, ANDROID_APP_PATH, EXECUTOR
+from helpers import report_to_sauce, take_screenshot_and_logcat, ANDROID_BASE_CAPS, EXECUTOR
class TestAndroidBasicInteractions():
@@ -14,20 +15,19 @@ class TestAndroidBasicInteractions():
@pytest.fixture(scope='function')
def driver(self, request, device_logger):
calling_request = request._pyfuncitem.name
+
+ caps = copy.copy(ANDROID_BASE_CAPS)
+ caps['name'] = calling_request
+ caps['appActivity'] = self.SEARCH_ACTIVITY
+
driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': ANDROID_APP_PATH,
- 'platformName': 'Android',
- 'automationName': 'UIAutomator2',
- 'platformVersion': os.getenv('ANDROID_PLATFORM_VERSION') or '7.1',
- 'deviceName': os.getenv('ANDROID_DEVICE_VERSION') or 'Android',
- 'appActivity': self.SEARCH_ACTIVITY
- }
+ desired_capabilities=caps
)
def fin():
- take_screenhot_and_logcat(driver, device_logger, calling_request)
+ report_to_sauce(driver.session_id)
+ take_screenshot_and_logcat(driver, device_logger, calling_request)
driver.quit()
request.addfinalizer(fin)

MODIFY
sample-code/python/test/test_android_create_session.py
sample-code/python/test/test_android_create_session.py
import unittest
import os
+import copy
+import sys
+
+from time import sleep
from appium import webdriver
-from helpers import take_screenhot_and_logcat, ANDROID_APP_PATH, EXECUTOR
-from selenium.common.exceptions import InvalidSessionIdException
+from helpers import report_to_sauce, ANDROID_BASE_CAPS, EXECUTOR
+from selenium.common.exceptions import WebDriverException
# Run standard unittest base.
-class TestAndroidSelectors(unittest.TestCase):
+class TestAndroidCreateSession(unittest.TestCase):
+ def tearDown(self):
+ report_to_sauce(self.driver.session_id)
+
+ def test_should_create_and_destroy_android_session(self):
+ caps = copy.copy(ANDROID_BASE_CAPS)
+ caps['name'] = 'test_should_create_and_destroy_android_session'
- def setUp(self):
self.driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': ANDROID_APP_PATH,
- 'platformName': 'Android',
- 'automationName': 'UIAutomator2',
- 'platformVersion': os.getenv('ANDROID_PLATFORM_VERSION') or '7.1',
- 'deviceName': os.getenv('ANDROID_DEVICE_VERSION') or 'Android',
- }
+ desired_capabilities=caps
)
self.driver.implicitly_wait(10)
- def test_should_create_and_destroy_android_session(self):
- activity = self.driver.current_activity
- pkg = self.driver.current_package
+ # make sure the right package and activity were started
+ self.assertEquals('io.appium.android.apis', self.driver.current_package)
+ self.assertEquals('.ApiDemos', self.driver.current_activity)
- self.assertEquals('io.appium.android.apis.ApiDemos', '{}{}'.format(pkg, activity))
self.driver.quit()
- with self.assertRaises(InvalidSessionIdException) as excinfo:
+ sleep(5)
+
+ # should not be able to use the driver anymore
+ with self.assertRaises(WebDriverException) as excinfo:
self.driver.title
- self.assertEquals('A session is either terminated or not started', excinfo.exception.msg)
+ self.assertTrue('has already finished' in str(excinfo.exception.msg))

MODIFY
sample-code/python/test/test_android_create_web_session.py
sample-code/python/test/test_android_create_web_session.py
-import pytest
+import unittest
import os
+import copy
+import sys
+
+from time import sleep
from appium import webdriver
-from helpers import take_screenhot_and_logcat, EXECUTOR
-from selenium.common.exceptions import InvalidSessionIdException
+from helpers import report_to_sauce, ANDROID_BASE_CAPS, EXECUTOR
+from selenium.common.exceptions import WebDriverException
+
+class TestAndroidCreateWebSession(unittest.TestCase):
+ def tearDown(self):
+ report_to_sauce(self.driver.session_id)
-class TestAndroidCreateWebSession():
+ def test_should_create_and_destroy_android_web_session(self):
+ caps = copy.copy(ANDROID_BASE_CAPS)
+ caps['name'] = 'test_should_create_and_destroy_android_web_session'
+ # can only specify one of `app` and `browserName`
+ caps['browserName'] = 'Chrome'
+ caps.pop('app')
- @pytest.fixture(scope='function')
- def driver(self, request, device_logger):
- calling_request = request._pyfuncitem.name
- driver = webdriver.Remote(
+ self.driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'platformName': 'Android',
- 'automationName': 'UIAutomator2',
- 'platformVersion': os.getenv('ANDROID_PLATFORM_VERSION') or '7.1',
- 'deviceName': os.getenv('ANDROID_DEVICE_VERSION') or 'Android',
- 'browserName': 'Chrome'
- }
+ desired_capabilities=caps
)
+ self.driver.implicitly_wait(10)
- def fin():
- take_screenhot_and_logcat(driver, device_logger, calling_request)
-
- request.addfinalizer(fin)
+ self.driver.get('https://www.google.com')
- driver.implicitly_wait(10)
- return driver
+ assert 'Google' == self.driver.title
- def test_should_create_and_destroy_android_session(self, driver):
- driver.get('https://www.google.com')
- title = driver.title
+ self.driver.quit()
- assert 'Google' == title
+ sleep(5)
- with pytest.raises(InvalidSessionIdException) as excinfo:
- driver.title
- assert 'A session is either terminated or not started' == excinfo.value.msg
+ with self.assertRaises(WebDriverException) as excinfo:
+ self.driver.title
+ self.assertTrue('has already finished' in str(excinfo.exception.msg))

MODIFY
sample-code/python/test/test_android_selectors.py
sample-code/python/test/test_android_selectors.py
import pytest
import os
+import copy
from appium import webdriver
-from helpers import take_screenhot_and_logcat, ANDROID_APP_PATH, EXECUTOR
+from helpers import report_to_sauce, take_screenshot_and_logcat, ANDROID_BASE_CAPS, EXECUTOR
-class TestAndroidBasicInteractions():
+class TestAndroidSelectors():
@pytest.fixture(scope='function')
def driver(self, request, device_logger):
calling_request = request._pyfuncitem.name
+
+ caps = copy.copy(ANDROID_BASE_CAPS)
+ caps['name'] = calling_request
driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': ANDROID_APP_PATH,
- 'platformName': 'Android',
- 'automationName': 'UIAutomator2',
- 'platformVersion': os.getenv('ANDROID_PLATFORM_VERSION') or '7.1',
- 'deviceName': os.getenv('ANDROID_DEVICE_VERSION') or 'Android'
- }
+ desired_capabilities=caps
)
def fin():
- take_screenhot_and_logcat(driver, device_logger, calling_request)
+ report_to_sauce(driver.session_id)
+ take_screenshot_and_logcat(driver, device_logger, calling_request)
driver.quit()
request.addfinalizer(fin)

MODIFY
sample-code/python/test/test_ios_basic_interactions.py
sample-code/python/test/test_ios_basic_interactions.py
import pytest
import os
+import copy
from appium import webdriver
-from helpers import take_screenhot_and_syslog, IOS_APP_PATH, EXECUTOR
+from helpers import report_to_sauce, take_screenshot_and_syslog, IOS_BASE_CAPS, EXECUTOR
class TestIOSBasicInteractions():
@@ -10,19 +11,18 @@ class TestIOSBasicInteractions():
@pytest.fixture(scope='function')
def driver(self, request, device_logger):
calling_request = request._pyfuncitem.name
+
+ caps = copy.copy(IOS_BASE_CAPS)
+ caps['name'] = calling_request
+
driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': IOS_APP_PATH,
- 'platformName': 'iOS',
- 'automationName': 'XCUITest',
- 'platformVersion': os.getenv('IOS_PLATFORM_VERSION') or '12.1',
- 'deviceName': os.getenv('IOS_DEVICE_NAME') or 'iPhone 8',
- }
+ desired_capabilities=caps
)
def fin():
- take_screenhot_and_syslog(driver, device_logger, calling_request)
+ report_to_sauce(driver.session_id)
+ take_screenshot_and_syslog(driver, device_logger, calling_request)
driver.quit()
request.addfinalizer(fin)

MODIFY
sample-code/python/test/test_ios_create_session.py
sample-code/python/test/test_ios_create_session.py
import unittest
import os
+import copy
+import sys
+
+from time import sleep
from appium import webdriver
-from helpers import take_screenhot_and_syslog, IOS_APP_PATH, EXECUTOR
-from selenium.common.exceptions import InvalidSessionIdException
+from helpers import report_to_sauce, IOS_BASE_CAPS, EXECUTOR
+from selenium.common.exceptions import WebDriverException
# Run standard unittest base.
-class TestIOSSelectors(unittest.TestCase):
+class TestIOSCreateSession(unittest.TestCase):
+ def tearDown(self):
+ report_to_sauce(self.driver.session_id)
+
+ def test_should_create_and_destroy_ios_session(self):
+ caps = copy.copy(IOS_BASE_CAPS)
+ caps['name'] = self.id()
- def setUp(self):
self.driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': IOS_APP_PATH,
- 'platformName': 'iOS',
- 'automationName': 'XCUITest',
- 'platformVersion': os.getenv('IOS_PLATFORM_VERSION') or '12.1',
- 'deviceName': os.getenv('IOS_DEVICE_NAME') or 'iPhone 8',
- }
+ desired_capabilities=caps
)
self.driver.implicitly_wait(10)
- def test_should_create_and_destroy_ios_session(self):
app_element = self.driver.find_element_by_class_name('XCUIElementTypeApplication')
- app_element_name = app_element.get_attribute('name')
+ self.assertEquals('TestApp', app_element.get_attribute('name'))
- self.assertEquals('TestApp', app_element_name)
self.driver.quit()
- with self.assertRaises(InvalidSessionIdException) as excinfo:
- self.driver.title
- self.assertEquals('A session is either terminated or not started', excinfo.exception.msg)
+ # pause a moment because Sauce Labs takes a bit to stop accepting requests
+ sleep(5)
+
+ with self.assertRaises(WebDriverException) as excinfo:
+ self.driver.find_element_by_class_name('XCUIElementTypeApplication')
+ self.assertTrue(
+ 'has already finished' in str(excinfo.exception.msg) or
+ 'Unhandled endpoint' in str(excinfo.exception.msg)
+ )

MODIFY
sample-code/python/test/test_ios_create_web_session.py
sample-code/python/test/test_ios_create_web_session.py
-import pytest
+import unittest
import os
+import copy
+import sys
+
+from time import sleep
from appium import webdriver
-from helpers import take_screenhot_and_syslog, EXECUTOR
-from selenium.common.exceptions import InvalidSessionIdException
+from helpers import report_to_sauce, IOS_BASE_CAPS, EXECUTOR
+from selenium.common.exceptions import WebDriverException
+
+class TestIOSCreateWebSession(unittest.TestCase):
+ def tearDown(self):
+ report_to_sauce(self.driver.session_id)
-class TestIOSCreateWebSession():
+ def test_should_create_and_destroy_ios_web_session(self):
+ caps = copy.copy(IOS_BASE_CAPS)
+ caps['name'] = self.id()
+ # can only specify one of `app` and `browserName`
+ caps['browserName'] = 'Safari'
+ caps.pop('app')
- @pytest.fixture(scope='function')
- def driver(self, request, device_logger):
- calling_request = request._pyfuncitem.name
- driver = webdriver.Remote(
+ self.driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'platformName': 'iOS',
- 'automationName': 'XCUITest',
- 'platformVersion': os.getenv('IOS_PLATFORM_VERSION') or '12.1',
- 'deviceName': os.getenv('IOS_DEVICE_NAME') or 'iPhone 8',
- 'browserName': 'Safari'
- }
+ desired_capabilities=caps
)
- def fin():
- take_screenhot_and_syslog(driver, device_logger, calling_request)
-
- request.addfinalizer(fin)
+ self.driver.get('https://www.google.com')
+ assert 'Google' == self.driver.title
- driver.implicitly_wait(10)
- return driver
+ self.driver.quit()
- def test_should_create_and_destroy_android_session(self, driver):
- driver.get('https://www.google.com')
- title = driver.title
+ sleep(5)
- assert 'Google' == title
- driver.quit()
-
- with pytest.raises(InvalidSessionIdException) as excinfo:
- driver.title
- assert 'A session is either terminated or not started' == excinfo.value.msg
+ with self.assertRaises(WebDriverException) as excinfo:
+ self.driver.title
+ self.assertTrue(
+ 'has already finished' in str(excinfo.exception.msg) or
+ 'Unhandled endpoint' in str(excinfo.exception.msg)
+ )

MODIFY
sample-code/python/test/test_ios_selectors.py
sample-code/python/test/test_ios_selectors.py
import pytest
import os
+import copy
from appium import webdriver
-from helpers import take_screenhot_and_syslog, IOS_APP_PATH, EXECUTOR
+from helpers import report_to_sauce, take_screenshot_and_syslog, IOS_BASE_CAPS, EXECUTOR
-class TestIOSBasicInteractions():
+class TestIOSSelectors():
@pytest.fixture(scope='function')
def driver(self, request, device_logger):
calling_request = request._pyfuncitem.name
+
+ caps = copy.copy(IOS_BASE_CAPS)
+ caps['name'] = calling_request
+
driver = webdriver.Remote(
command_executor=EXECUTOR,
- desired_capabilities={
- 'app': IOS_APP_PATH,
- 'platformName': 'iOS',
- 'automationName': 'XCUITest',
- 'platformVersion': os.getenv('IOS_PLATFORM_VERSION') or '12.1',
- 'deviceName': os.getenv('IOS_DEVICE_NAME') or 'iPhone 8',
- }
+ desired_capabilities=caps
)
def fin():
- take_screenhot_and_syslog(driver, device_logger, calling_request)
+ report_to_sauce(driver.session_id)
+ take_screenshot_and_syslog(driver, device_logger, calling_request)
driver.quit()
request.addfinalizer(fin)
@@ -40,7 +40,7 @@ class TestIOSBasicInteractions():
def test_should_find_elements_by_nspredicate(self, driver):
all_visible_elements = driver.find_elements_by_ios_predicate('visible = 1')
- assert 25 <= len(all_visible_elements)
+ assert 24 <= len(all_visible_elements)
def test_should_find_elements_by_class_chain(self, driver):
window_element = driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow[1]/*')
@@ -48,4 +48,4 @@ class TestIOSBasicInteractions():
def test_should_find_elements_by_xpath(self, driver):
action_bar_container_elements = driver.find_elements_by_xpath('//XCUIElementTypeWindow//XCUIElementTypeButton')
- assert 8 == len(action_bar_container_elements)
+ assert 7 == len(action_bar_container_elements)

Update python sample code (#12681)