commit a5e009607e5dfdab9bc219330534352e49c31e26 Author: butomo1989 Date: Thu Dec 22 14:29:57 2016 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a666536 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea/* +.DS_Store +*.pyc + +# Coverage +.coverage +coverage.xml +xunit.xml +coverage/* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ce8c9dc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: + - "2.7" +install: "pip install -r requirements.txt" +script: nosetests +branches: + only: + - master +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82fe920 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +FROM ubuntu:16.04 + +#======================= +# General Configuration +#======================= +RUN apt-get update && apt-get upgrade -y +RUN apt-get install wget -y + +#============== +# Install Java +#============== +RUN apt-get install openjdk-8-jdk -y +ENV JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64/jre" +ENV PATH="${PATH}:${JAVA_HOME}/bin" + +#===================== +# Install Android SDK +#===================== +RUN wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz +RUN tar -xvzf android-sdk_r24.4.1-linux.tgz +ENV ANDROID_HOME="/android-sdk-linux" +ENV PATH="${PATH}:${ANDROID_HOME}/tools" + +#===================================================== +# Install Platform-tools, Build-tools +# To see list of available packages: android list sdk +#===================================================== +RUN echo y | android update sdk --no-ui --filter 2,3 +ENV PATH="${PATH}:${ANDROID_HOME}/platform-tools" +ENV PATH="${PATH}:${ANDROID_HOME}/build-tools" + +#================================================== +# Fix issue regarding 64bit while running emulator +#================================================== +RUN dpkg --add-architecture i386 +RUN apt-get update +RUN apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 -y +ENV ANDROID_EMULATOR_FORCE_32BIT=true +RUN adb start-server + +#============================================ +# Install nodejs, npm, appium, appium-doctor +#============================================ +RUN apt-get install npm nodejs-legacy -y +ENV APPIUM_VERSION 1.6.3 +RUN npm install -g appium@$APPIUM_VERSION + +#=================== +# Run docker-appium +#=================== +COPY service /service +WORKDIR /service +CMD python start.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c1d2dee --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016 budi utomo + +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/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..02cfc4b --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1 @@ +Budi Utomo diff --git a/README.md b/README.md new file mode 100644 index 0000000..75223cb --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +Docker-Android-Appium +===================== + +Android emulator and Appium server in docker solution. + +Requirements +------------ + +Docker is installed in your system. + +Quick Start +----------- + +1. Run docker-appium with command: + +```bash +docker run -d -p 4723:4723 -v :/target_apk -e ANDROID_VERSION= --name appium-container butomo1989/docker-appium +``` + +***Note: There is an example apk in folder example.*** + +An Example: + +```bash +docker run -d -p 4723:4723 -v $PWD/example/sample_apk:/target_apk -e ANDROID_VERSION=4.2.2 --name appium-container docker-android-appium +``` + +2. See the docker logs with command: + +```bash +docker logs appium-container -f +``` + +3. Wait until you see this following example messages in logs that showing that appium server is ready to use: + +```bash +INFO:android_appium:Android emulator is created +INFO:android_appium:android emulator name: emulator_4.2.2 +[Appium] Welcome to Appium v1.6.3 +[Appium] Appium REST http interface listener started on 0.0.0.0:4723 +``` + +4. Run your UI tests by using docker-appium. + +***Note: There is an example UITests in folder example.*** diff --git a/example/android/python/README.md b/example/android/python/README.md new file mode 100644 index 0000000..38ed58f --- /dev/null +++ b/example/android/python/README.md @@ -0,0 +1,14 @@ +Docker-Android-Appium Sample +---------------------------- + +Example UI test to use docker-appium. + +Requirements +============ +1. docker-appium +2. python + +Quick Start +=========== +1. pip install -r requirements +2. python android_simple.py diff --git a/example/android/python/app_simple.py b/example/android/python/app_simple.py new file mode 100644 index 0000000..f16a046 --- /dev/null +++ b/example/android/python/app_simple.py @@ -0,0 +1,34 @@ +import unittest + +from appium import webdriver + + +class SimpleAndroidUITests(unittest.TestCase): + + def setUp(self): + desired_caps = { + 'platformName': 'Android', + 'deviceName': 'Android Emulator', + 'platformVersion': '4.2', + 'app': '/target_apk/sample_apk_debug.apk', + 'avd': 'emulator_4.2.2' + } + self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) + + def tearDown(self): + self.driver.quit() + + def test_calculation(self): + text_fields = self.driver.find_elements_by_class_name('android.widget.EditText') + text_fields[0].send_keys(4) + text_fields[1].send_keys(6) + + btn_calculate = self.driver.find_element_by_class_name('android.widget.Button') + btn_calculate.click() + + self.assertEqual('10', text_fields[2].text) + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(SimpleAndroidUITests) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/example/android/python/requirements.txt b/example/android/python/requirements.txt new file mode 100644 index 0000000..4b63864 --- /dev/null +++ b/example/android/python/requirements.txt @@ -0,0 +1 @@ +Appium-Python-Client==0.23 diff --git a/example/sample_apk/sample_apk_debug.apk b/example/sample_apk/sample_apk_debug.apk new file mode 100644 index 0000000..fcaa8de Binary files /dev/null and b/example/sample_apk/sample_apk_debug.apk differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..21974b7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +autopep8==1.2.4 +coverage==4.2 +mock==2.0.0 +nose==1.3.7 diff --git a/service/__init__.py b/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service/start.py b/service/start.py new file mode 100644 index 0000000..4bf7545 --- /dev/null +++ b/service/start.py @@ -0,0 +1,110 @@ +import logging +import os +import re +import subprocess + +logging.basicConfig() +logger = logging.getLogger('android_appium') + + +def run(): + """ + Run Android emulator and Appium server. + + """ + android_version = os.getenv('ANDROID_VERSION', '4.2.2') + create_android_emulator(android_version) + + emulator_name = 'emulator_{version}'.format(version=android_version) + + logger.info('android emulator name: {name} '.format(name=emulator_name)) + # TODO: check android emulator is ready to use + cmd_run = 'emulator -avd {name} -no-audio -no-window & appium'.format(name=emulator_name) + subprocess.check_call(cmd_run, shell=True) + + +def get_available_sdk_packages(): + """ + Get list of available sdk packages. + + :return: List of available packages. + :rtype: bytearray + """ + logger.info('List of Android SDK: ') + cmd = ['android', 'list', 'sdk'] + + output_str = subprocess.check_output(cmd) + logger.info(output_str) + + return [output.strip() for output in output_str.split('\n')] if output_str else None + + +def get_item_position(keyword, items): + """ + Get position of item in array by given keyword. + + :return: Item position. + :rtype: int + """ + pos = 0 + for p, v in enumerate(items): + if keyword in v: + pos = p + break # Get the first item that match with keyword + return pos + + +def create_android_emulator(android_version): + """ + Create android emulator based on given android version. + + It include installation of sdk package and its armeabi v7a. + To see list of available targets: android list targets + To see list to avd: android list avd + + :param android_version: android version + :type android_version: str + """ + try: + packages = get_available_sdk_packages() + + if packages: + item_pos = get_item_position(android_version, packages) + logger.info('item position: {pos}'.format(pos=item_pos)) + item = packages[item_pos] + + item_info = item.split('-') + package_number = item_info[0] + api_version = re.search('%s(.*)%s' % ('API', ','), item_info[1]).group(1).strip() + logger.info( + 'Package number: {number}, API version: {version}'.format(number=package_number, version=api_version)) + + # Install SDK package + logger.info('Installing SDK package...') + cmd_sdk = 'echo y | android update sdk --no-ui --filter {number}'.format(number=package_number) + subprocess.check_call(cmd_sdk, shell=True) + logger.info('Installation completed') + + # Install armeabi v7a + logger.info('Installing its armeabi...') + cmd_arm = 'echo y | android update sdk --no-ui -a --filter sys-img-armeabi-v7a-android-{api}'.format( + api=api_version) + subprocess.check_call(cmd_arm, shell=True) + logger.info('Installation completed') + + # Create android emulator + logger.info('Creating android emulator...') + cmd_emu = 'echo no | android create avd -f -n emulator_{version} -t android-{api} --abi armeabi-v7a'.format( + version=android_version, api=api_version) + subprocess.check_call(cmd_emu, shell=True) + logger.info('Android emulator is created') + else: + raise RuntimeError('Packages is empty!') + + except IndexError as i_err: + logger.error(i_err) + + +if __name__ == '__main__': + logger.setLevel(logging.INFO) + run() diff --git a/service/tests/__init__.py b/service/tests/__init__.py new file mode 100644 index 0000000..fbc8d7c --- /dev/null +++ b/service/tests/__init__.py @@ -0,0 +1,19 @@ +"""Unit test for start.py.""" +from unittest import TestCase + +import mock + +from service import start + + +class TestService(TestCase): + """Unit test class to test method run.""" + + @mock.patch('service.start.create_android_emulator') + @mock.patch('subprocess.check_call') + def test_service(self, mocked_creation, mocked_subprocess): + self.assertFalse(mocked_creation.called) + self.assertFalse(mocked_subprocess.called) + start.run() + self.assertTrue(mocked_creation.called) + self.assertTrue(mocked_subprocess.called) diff --git a/service/tests/test_android_emulator.py b/service/tests/test_android_emulator.py new file mode 100644 index 0000000..831b5b6 --- /dev/null +++ b/service/tests/test_android_emulator.py @@ -0,0 +1,34 @@ +"""Unit test for start.py.""" +from unittest import TestCase + +import mock + +from service import start + + +@mock.patch('service.start.get_available_sdk_packages') +class TestRunService(TestCase): + """Unit test class to test method create_android_emulator.""" + + def test_create_emulator(self, mocked_packages): + mocked_packages.return_value = ['9- SDK Platform Android 4.4.2, API 19, revision 4', + '10- SDK Platform Android 4.3.1, API 18, revision 3', + '11- SDK Platform Android 4.2.2, API 17, revision 3'] + with mock.patch('subprocess.check_call') as mocked_subprocess: + self.assertFalse(mocked_subprocess.called) + android_version = '4.2.2' + start.create_android_emulator(android_version) + self.assertTrue(mocked_subprocess.called) + + def test_empty_packages(self, mocked_packages): + mocked_packages.return_value = None + with self.assertRaises(RuntimeError): + start.create_android_emulator('4.2.2') + + def test_index_error(self, mocked_packages): + mocked_packages.return_value = ['9 SDK Platform Android 4.4.2, API 19, revision 4', + '10 SDK Platform Android 4.3.1, API 18, revision 3', + '11 SDK Platform Android 4.2.2, API 17, revision 3'] + android_version = '4.2.2' + start.create_android_emulator(android_version) + self.assertRaises(IndexError) diff --git a/service/tests/test_available_packages.py b/service/tests/test_available_packages.py new file mode 100644 index 0000000..19f13e4 --- /dev/null +++ b/service/tests/test_available_packages.py @@ -0,0 +1,28 @@ +"""Unit test for start.py.""" +from unittest import TestCase + +import mock + +from service import start + + +class TestAvailablePackages(TestCase): + """Unit test class to test method get_available_sdk_packages.""" + + @mock.patch('subprocess.check_output') + def test_valid_output(self, mocked_output): + mocked_output.return_value = 'package 1 \n package 2' + output = start.get_available_sdk_packages() + self.assertEqual(['package 1', 'package 2'], output) + + @mock.patch('subprocess.check_output') + def test_without_line_break(self, mocked_output): + mocked_output.return_value = 'package 1, package 2' + output = start.get_available_sdk_packages() + self.assertEqual(['package 1, package 2'], output) + + @mock.patch('subprocess.check_output') + def test_empty_string(self, mocked_output): + mocked_output.return_value = None + output = start.get_available_sdk_packages() + self.assertEqual(None, output) diff --git a/service/tests/test_item_position.py b/service/tests/test_item_position.py new file mode 100644 index 0000000..e894a42 --- /dev/null +++ b/service/tests/test_item_position.py @@ -0,0 +1,27 @@ +"""Unit test for start.py.""" +from unittest import TestCase + +from service import start + + +class TestItemPosition(TestCase): + """Unit test class to test method get_item_position.""" + + def setUp(self): + self.items = ['android 4.1', 'android 4.2.2', 'android 4.3', 'android 4.4', 'android 4.4.2'] + + def test_valid_params(self): + keyword = '4.2' + output = start.get_item_position(keyword, self.items) + self.assertEqual(1, output) + + def test_invalid_keyword(self): + keyword = 'fake' + output = start.get_item_position(keyword, self.items) + self.assertEqual(0, output) + + def test_empty_array(self): + items = [] + keyword = '4.2' + output = start.get_item_position(keyword, items) + self.assertEqual(0, output) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..893bcbb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,17 @@ +[nosetests] +cover-xml=true +cover-xml-file=coverage.xml +with-coverage=true +cover-package=service +cover-erase=true +with-xunit=true +xunit-file=xunit.xml +cover-html=true +cover-html-dir=coverage + +[pep257] +inherit = false +ignore = D100,D101,D102,D103,D104,D203 + +[flake8] +max-line-length = 120