First commit
This commit is contained in:
commit
a5e009607e
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.idea/*
|
||||
.DS_Store
|
||||
*.pyc
|
||||
|
||||
# Coverage
|
||||
.coverage
|
||||
coverage.xml
|
||||
xunit.xml
|
||||
coverage/*
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -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)
|
53
Dockerfile
Normal file
53
Dockerfile
Normal file
|
@ -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
|
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -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.
|
1
MAINTAINERS
Normal file
1
MAINTAINERS
Normal file
|
@ -0,0 +1 @@
|
|||
Budi Utomo <budi.ut.1989@gmail.com>
|
45
README.md
Normal file
45
README.md
Normal file
|
@ -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 <apk_path_that_will_be_tested>:/target_apk -e ANDROID_VERSION=<target_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.***
|
14
example/android/python/README.md
Normal file
14
example/android/python/README.md
Normal file
|
@ -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
|
34
example/android/python/app_simple.py
Normal file
34
example/android/python/app_simple.py
Normal file
|
@ -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)
|
1
example/android/python/requirements.txt
Normal file
1
example/android/python/requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Appium-Python-Client==0.23
|
BIN
example/sample_apk/sample_apk_debug.apk
Normal file
BIN
example/sample_apk/sample_apk_debug.apk
Normal file
Binary file not shown.
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
autopep8==1.2.4
|
||||
coverage==4.2
|
||||
mock==2.0.0
|
||||
nose==1.3.7
|
0
service/__init__.py
Normal file
0
service/__init__.py
Normal file
110
service/start.py
Normal file
110
service/start.py
Normal file
|
@ -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()
|
19
service/tests/__init__.py
Normal file
19
service/tests/__init__.py
Normal file
|
@ -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)
|
34
service/tests/test_android_emulator.py
Normal file
34
service/tests/test_android_emulator.py
Normal file
|
@ -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)
|
28
service/tests/test_available_packages.py
Normal file
28
service/tests/test_available_packages.py
Normal file
|
@ -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)
|
27
service/tests/test_item_position.py
Normal file
27
service/tests/test_item_position.py
Normal file
|
@ -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)
|
17
setup.cfg
Normal file
17
setup.cfg
Normal file
|
@ -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
|
Loading…
Reference in a new issue