mirror of
https://github.com/budtmo/docker-android.git
synced 2026-07-31 04:07:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564a1491ff | ||
|
|
a89d4679be | ||
|
|
9290e90e3c | ||
|
|
136b49fab8 | ||
|
|
c5651e78f9 | ||
|
|
76ae526591 | ||
|
|
46805d6a81 | ||
|
|
6302de6bc4 | ||
|
|
1e1c60fc89 | ||
|
|
2a12e66077 | ||
|
|
f2a418d712 | ||
|
|
44dcc5b166 | ||
|
|
2e88c06124 | ||
|
|
6b34c74a25 | ||
|
|
1fc2c74d8c | ||
|
|
ffbafe87e8 | ||
|
|
62fa6a05c8 | ||
|
|
77542f5b45 | ||
|
|
39df955c26 | ||
|
|
8da596a8f1 | ||
|
|
7f55b248fd | ||
|
|
c434ae7770 | ||
|
|
ff9f9268e3 | ||
|
|
27e95cd197 | ||
|
|
1f1224599e | ||
|
|
846cd2ed09 | ||
|
|
b5fbe9777e | ||
|
|
21ffb20ca6 | ||
|
|
c0368d3d71 | ||
|
|
6e5ed8386a | ||
|
|
602dcf15a7 | ||
|
|
1dd79c6188 | ||
|
|
7abe6c19d8 | ||
|
|
eb91462bd4 | ||
|
|
ac251e7704 | ||
|
|
b997803064 | ||
|
|
88dbde577e | ||
|
|
a51e451a1f | ||
|
|
0f8d8eb98b | ||
|
|
97b3ca2c28 | ||
|
|
7b599fe7ea | ||
|
|
34846cd059 | ||
|
|
8df9081573 | ||
|
|
9f0c0dcfd2 | ||
|
|
e8186fb6bf | ||
|
|
f33fc1906c | ||
|
|
be3150f39f | ||
|
|
5688826f1a | ||
|
|
7064a9bc2c | ||
|
|
4e44e68946 | ||
|
|
2cb2bd6256 | ||
|
|
3af8eb3f48 | ||
|
|
55a628a673 | ||
|
|
667787bada | ||
|
|
cc5806bf71 | ||
|
|
db58150cf8 | ||
|
|
26206e43bd | ||
|
|
a0851223da | ||
|
|
1b05bfafdc | ||
|
|
e1ff20db98 | ||
|
|
df2dfbf6e1 | ||
|
|
03a6754869 | ||
|
|
99efbd21a5 | ||
|
|
05d3622c64 | ||
|
|
232ed8bbe6 | ||
|
|
20b1c6f71b | ||
|
|
4417553044 | ||
|
|
050144f8da | ||
|
|
5b25d2fb79 | ||
|
|
b5aa9c1290 | ||
|
|
7e8a082936 | ||
|
|
0eb43bee45 | ||
|
|
0702f34bd3 | ||
|
|
fbd31b9076 | ||
|
|
40864137ee | ||
|
|
c5cce78990 | ||
|
|
5002255cd8 | ||
|
|
f570984135 | ||
|
|
80e22c2b1c | ||
|
|
064e9c4f37 | ||
|
|
7db92991ab | ||
|
|
47eafe24e1 | ||
|
|
8875d83305 | ||
|
|
984f5ed69e | ||
|
|
a02375de57 | ||
|
|
cebf34d8ae | ||
|
|
6da341bf6b | ||
|
|
5f1ea4c1e5 | ||
|
|
b35f538bf3 | ||
|
|
67321bdac7 | ||
|
|
baefa67e3f | ||
|
|
58f64cd2ee | ||
|
|
2b56bab0bc | ||
|
|
05ce63f360 | ||
|
|
fd4089d98e | ||
|
|
363fc77b87 | ||
|
|
b9f02023b4 | ||
|
|
cabad42452 | ||
|
|
deb9180bc7 | ||
|
|
9d85ad345e | ||
|
|
9d3f37ca0c | ||
|
|
8cd6bfb3ef | ||
|
|
4f0ddc972a | ||
|
|
703c1216cf | ||
|
|
0ecc00587e |
@@ -7,35 +7,23 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run_test:
|
run_test:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.8"]
|
python-version: ["3.8"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Run unit-test
|
||||||
uses: actions/setup-python@v4
|
run: script -e -c "./app.sh test emulator test 11.0 && sudo mv tmp/* ."
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
cd cli
|
|
||||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
||||||
|
|
||||||
- name: Run test
|
|
||||||
run: |
|
|
||||||
cd cli && nosetests -v
|
|
||||||
|
|
||||||
release_base:
|
release_base:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
needs: run_test
|
needs: run_test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get release version
|
- name: Get release version
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
@@ -47,18 +35,27 @@ jobs:
|
|||||||
docker logout
|
docker logout
|
||||||
|
|
||||||
release_emulator:
|
release_emulator:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
needs: release_base
|
needs: release_base
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
android: ["9.0", "10.0", "11.0", "12.0", "13.0"]
|
android: ["9.0", "10.0", "11.0", "12.0", "13.0", "14.0"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Get release version
|
- name: Get release version
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up extension
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.extension }}" > extension.sh
|
||||||
|
chmod 700 extension.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Build and push emulator image ${{ matrix.android }} (${RELEASE_VERSION})
|
- name: Build and push emulator image ${{ matrix.android }} (${RELEASE_VERSION})
|
||||||
run: |
|
run: |
|
||||||
docker login -u=${{secrets.DOCKER_USERNAME}} -p=${{secrets.DOCKER_PASSWORD}}
|
docker login -u=${{secrets.DOCKER_USERNAME}} -p=${{secrets.DOCKER_PASSWORD}}
|
||||||
@@ -66,15 +63,24 @@ jobs:
|
|||||||
docker logout
|
docker logout
|
||||||
|
|
||||||
release_genymotion:
|
release_genymotion:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
needs: release_base
|
needs: release_base
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Get release version
|
- name: Get release version
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up extension
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.extension }}" > extension.sh
|
||||||
|
chmod 700 extension.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Build and push genymotion image (${RELEASE_VERSION})
|
- name: Build and push genymotion image (${RELEASE_VERSION})
|
||||||
run: |
|
run: |
|
||||||
docker login -u=${{secrets.DOCKER_USERNAME}} -p=${{secrets.DOCKER_PASSWORD}}
|
docker login -u=${{secrets.DOCKER_USERNAME}} -p=${{secrets.DOCKER_PASSWORD}}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Run Test on Demand
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repo
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set up extension
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.extension }}" > extension.sh
|
||||||
|
chmod 700 extension.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build base image
|
||||||
|
run: script -e -c "./app.sh build base test"
|
||||||
|
|
||||||
|
- name: Build sample image
|
||||||
|
run: script -e -c "./app.sh build emulator test 11.0"
|
||||||
|
|
||||||
|
- name: Run unit-test
|
||||||
|
run: script -e -c "./app.sh test emulator test 11.0 && sudo mv tmp/* ."
|
||||||
|
|
||||||
|
- name: Publish test result
|
||||||
|
run: bash <(curl -s https://codecov.io/bash)
|
||||||
@@ -4,21 +4,31 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_test:
|
build_and_test:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.actor == 'budtmo'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set up extension
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.extension }}" > extension.sh
|
||||||
|
chmod 700 extension.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
run: script -e -c "./app.sh build base test"
|
run: script -e -c "./app.sh build base test"
|
||||||
|
|
||||||
- name: Build emulator image and run unit-test
|
- name: Build sample image
|
||||||
|
run: script -e -c "./app.sh build emulator test 11.0"
|
||||||
|
|
||||||
|
- name: Run unit-test
|
||||||
run: script -e -c "./app.sh test emulator test 11.0 && sudo mv tmp/* ."
|
run: script -e -c "./app.sh test emulator test 11.0 && sudo mv tmp/* ."
|
||||||
|
|
||||||
- name: Publish test result
|
- name: Publish test result
|
||||||
|
|||||||
+1
-1
@@ -17,4 +17,4 @@ tmp/
|
|||||||
|
|
||||||
# Dev-files
|
# Dev-files
|
||||||
n*.txt
|
n*.txt
|
||||||
test-*.sh
|
e*.sh
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
Docker-Android is a docker image built to be used for everything related to Android. It can be used for Application development and testing (native, web and hybrid-app).
|
Docker-Android is a docker image built to be used for everything related to Android. It can be used for Application development and testing (native, web and hybrid-app).
|
||||||
|
|
||||||
Advantages of using this projects
|
Advantages of using this project
|
||||||
---------------------------------
|
--------------------------------
|
||||||
1. Emulator with different device profile and skins, such as Samsung Galaxy S6, LG Nexus 4, HTC Nexus One and more.
|
1. Emulator with different device profile and skins, such as Samsung Galaxy S6, LG Nexus 4, HTC Nexus One and more.
|
||||||
2. Support vnc to be able to see what happen inside docker container
|
2. Support vnc to be able to see what happen inside docker container
|
||||||
3. Support log sharing feature where all logs can be accessed from web-UI
|
3. Support log sharing feature where all logs can be accessed from web-UI
|
||||||
4. Ability to control emulator from outside container by using adb connect
|
4. Ability to control emulator from outside container by using adb connect
|
||||||
5. Integrated with other cloud solutions, e.g. [Genymotion Cloud](https://www.genymotion.com/cloud/)
|
5. Integrated with other cloud solutions, e.g. [Genymotion Cloud](https://www.genymotion.com/cloud/)
|
||||||
6. It can be used to build Android project
|
6. It can be used to build Android project
|
||||||
@@ -26,6 +26,7 @@ List of Docker-Images
|
|||||||
|11.0|30|budtmo/docker-android:emulator_11.0|budtmo/docker-android:emulator_11.0_<release_version>|
|
|11.0|30|budtmo/docker-android:emulator_11.0|budtmo/docker-android:emulator_11.0_<release_version>|
|
||||||
|12.0|32|budtmo/docker-android:emulator_12.0|budtmo/docker-android:emulator_12.0_<release_version>|
|
|12.0|32|budtmo/docker-android:emulator_12.0|budtmo/docker-android:emulator_12.0_<release_version>|
|
||||||
|13.0|33|budtmo/docker-android:emulator_13.0|budtmo/docker-android:emulator_13.0_<release_version>|
|
|13.0|33|budtmo/docker-android:emulator_13.0|budtmo/docker-android:emulator_13.0_<release_version>|
|
||||||
|
|14.0|34|budtmo/docker-android:emulator_14.0|budtmo/docker-android:emulator_14.0_<release_version>|
|
||||||
|-|-|budtmo/docker-android:genymotion|budtmo/docker-android:genymotion_<release_version>|
|
|-|-|budtmo/docker-android:genymotion|budtmo/docker-android:genymotion_<release_version>|
|
||||||
|
|
||||||
List of Devices
|
List of Devices
|
||||||
@@ -44,6 +45,7 @@ Phone | Nexus 5
|
|||||||
Phone | Nexus One
|
Phone | Nexus One
|
||||||
Phone | Nexus S
|
Phone | Nexus S
|
||||||
Tablet | Nexus 7
|
Tablet | Nexus 7
|
||||||
|
Tablet | Pixel C
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
@@ -73,6 +75,14 @@ Quick Start
|
|||||||
docker exec -it android-container cat device_status
|
docker exec -it android-container cat device_status
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Persisting data
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The default behaviour is to destroy the emulated device on container restart. To persist data, you need to mount a volume at `/home/androidusr`:
|
||||||
|
```
|
||||||
|
docker run -v data:/home/androidusr budtmo/docker-android:emulator_11.0
|
||||||
|
```
|
||||||
|
|
||||||
WSL2 Hardware acceleration (Windows 11 only)
|
WSL2 Hardware acceleration (Windows 11 only)
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -86,7 +96,7 @@ Credit goes to [Guillaume - The Parallel Interface blog](https://www.paralint.co
|
|||||||
sudo usermod -a -G kvm ${USER}
|
sudo usermod -a -G kvm ${USER}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add necessary flags to `/etc/wsl2.conf` to their respective sections.
|
2. Add necessary flags to `/etc/wsl.conf` to their respective sections.
|
||||||
```
|
```
|
||||||
[boot]
|
[boot]
|
||||||
command = /bin/bash -c 'chown -v root:kvm /dev/kvm && chmod 660 /dev/kvm'
|
command = /bin/bash -c 'chown -v root:kvm /dev/kvm && chmod 660 /dev/kvm'
|
||||||
@@ -153,6 +163,7 @@ The differences between normal version and pro version are:
|
|||||||
|user-behavior-analytics|Yes|No|-|
|
|user-behavior-analytics|Yes|No|-|
|
||||||
|proxy|No|Yes|Set up company proxy on Android emulator on fly|
|
|proxy|No|Yes|Set up company proxy on Android emulator on fly|
|
||||||
|language|No|Yes|Set up language on Android emulator on fly|
|
|language|No|Yes|Set up language on Android emulator on fly|
|
||||||
|
|Newer Android version|No|Yes|Support other newer Android version e.g. Android 15, Android 16, etc|
|
||||||
|root-privileged|No|Yes|Able to run command with security privileged|
|
|root-privileged|No|Yes|Able to run command with security privileged|
|
||||||
|headless-mode|No|Yes|Save resources by using headless mode|
|
|headless-mode|No|Yes|Save resources by using headless mode|
|
||||||
|Selenium 4.x integration|No|Yes|Running Appium UI-Tests againt one (Selenium Hub) endpoint for Android- and iOS emulator(s) / device(s)|
|
|Selenium 4.x integration|No|Yes|Running Appium UI-Tests againt one (Selenium Hub) endpoint for Android- and iOS emulator(s) / device(s)|
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${p}" == *"emulator"* ]]; then
|
if [[ "${p}" == *"emulator"* ]]; then
|
||||||
supported_android_version=("9.0" "10.0" "11.0" "12.0" "13.0")
|
supported_android_version=("9.0" "10.0" "11.0" "12.0" "13.0" "14.0")
|
||||||
declare -A api_levels=(
|
declare -A api_levels=(
|
||||||
["9.0"]=28
|
["9.0"]=28
|
||||||
["10.0"]=29
|
["10.0"]=29
|
||||||
["11.0"]=30
|
["11.0"]=30
|
||||||
["12.0"]=32
|
["12.0"]=32
|
||||||
["13.0"]=33
|
["13.0"]=33
|
||||||
|
["14.0"]=34
|
||||||
)
|
)
|
||||||
|
|
||||||
# To get the last index
|
# To get the last index
|
||||||
@@ -80,9 +81,15 @@ echo "${IMAGE_NAME_SPECIFIC_RELEASE} or ${IMAGE_NAME_LATEST} "
|
|||||||
|
|
||||||
function build() {
|
function build() {
|
||||||
# autopep8 --recursive --exclude=.git,__pycache__,venv --max-line-length=120 --in-place .
|
# autopep8 --recursive --exclude=.git,__pycache__,venv --max-line-length=120 --in-place .
|
||||||
cmd="docker build -t ${IMAGE_NAME_SPECIFIC_RELEASE} --build-arg DOCKER_ANDROID_VERSION=${r_v} "
|
cmd="docker build --no-cache -t ${IMAGE_NAME_SPECIFIC_RELEASE} --build-arg DOCKER_ANDROID_VERSION=${r_v} "
|
||||||
if [ -n "${a_v}" ]; then
|
if [ -n "${a_v}" ]; then
|
||||||
cmd+="--build-arg EMULATOR_ANDROID_VERSION=${a_v} --build-arg EMULATOR_API_LEVEL=${a_l} "
|
DOCKER_BUILDKIT=1
|
||||||
|
cmd="${cmd} --secret id=extension,src=extension.sh --build-arg EMULATOR_ANDROID_VERSION=${a_v} --build-arg EMULATOR_API_LEVEL=${a_l} "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${p}" == *"genymotion"* ]]; then
|
||||||
|
DOCKER_BUILDKIT=1
|
||||||
|
cmd="${cmd} --secret id=extension,src=extension.sh "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cmd+="-f ${FOLDER_PATH} ."
|
cmd+="-f ${FOLDER_PATH} ."
|
||||||
@@ -96,18 +103,14 @@ function build() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
cli_path="/home/androidusr/docker-android/cli"
|
tmp_folder="/app/tmp"
|
||||||
results_path="test-results"
|
|
||||||
tmp_folder="tmp"
|
|
||||||
|
|
||||||
mkdir -p tmp
|
mkdir -p tmp
|
||||||
build
|
docker run -it --rm -v "$PWD":/app -w /app python:3.12-slim bash \
|
||||||
docker run -it --rm --name test --entrypoint /bin/bash \
|
-c "cd cli && rm -rf ${tmp_folder}/* && \
|
||||||
-v $PWD/${tmp_folder}:${cli_path}/${tmp_folder} ${IMAGE_NAME_SPECIFIC_RELEASE} \
|
pip install --upgrade pip && pip install -r requirements.txt && \
|
||||||
-c "cd ${cli_path} && sudo rm -rf ${tmp_folder}/* && \
|
PYTHONPATH=src pytest -v && mv test-results/* ${tmp_folder}/ && chown -R 1300:1301 ${tmp_folder} && \
|
||||||
nosetests -v && sudo mv .coverage ${tmp_folder} && \
|
chmod a+x -R ${tmp_folder}"
|
||||||
sudo cp -r ${results_path}/* ${tmp_folder} && sudo chown -R 1300:1301 ${tmp_folder} &&
|
|
||||||
sudo chmod a+x -R ${tmp_folder}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function push() {
|
function push() {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
autopep8==2.0.4
|
autopep8==2.3.2
|
||||||
click==8.1.7
|
click==8.2.1
|
||||||
coverage==7.3.2
|
coverage==7.10.7
|
||||||
mock==5.1.0
|
mock==5.2.0
|
||||||
nose==1.3.7
|
pytest==8.4.2
|
||||||
requests==2.31.0
|
pytest-cov==7.0.0
|
||||||
|
pytest-xdist==3.8.0
|
||||||
|
requests==2.32.5
|
||||||
|
setuptools==80.9.0
|
||||||
|
|||||||
+7
-10
@@ -1,10 +1,7 @@
|
|||||||
[nosetests]
|
[tool:pytest]
|
||||||
cover-xml=true
|
addopts =
|
||||||
cover-xml-file=test-results/coverage.xml
|
--cov=src
|
||||||
with-coverage=true
|
--cov-report=html:test-results/html
|
||||||
cover-package=src
|
--cov-report=xml:test-results/coverage.xml
|
||||||
cover-erase=true
|
--junit-xml=test-results/junit-report.xml
|
||||||
with-xunit=true
|
testpaths = src/tests
|
||||||
xunit-file=test-results/xunit.xml
|
|
||||||
cover-html=true
|
|
||||||
cover-html-dir=test-results/coverage
|
|
||||||
|
|||||||
+6
-4
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
app_version = os.getenv("DOCKER_ANDROID_VERSION", "test-version")
|
app_version = os.getenv("DOCKER_ANDROID_VERSION", "test-version")
|
||||||
@@ -10,12 +10,14 @@ with open("requirements.txt", "r") as f:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="docker-android",
|
name="docker-android",
|
||||||
version=app_version,
|
version="0.1",
|
||||||
url="https://github.com/budtmo/docker-android",
|
url="https://github.com/budtmo/docker-android",
|
||||||
description="CLI for docker-android",
|
description="CLI for docker-android",
|
||||||
author="Budi Utomo",
|
author="Budi Utomo",
|
||||||
author_email="budtmo.os@gmail.com",
|
author_email="budtmo.os@gmail.com",
|
||||||
install_requires=reqs,
|
install_requires=reqs,
|
||||||
|
packages=find_packages(where="src"),
|
||||||
|
package_dir={"": "src"},
|
||||||
py_modules=["cli", "docker-android"],
|
py_modules=["cli", "docker-android"],
|
||||||
entry_points={"console_scripts": "docker-android=src.app:cli"}
|
entry_points={"console_scripts": "docker-android=app:cli"}
|
||||||
)
|
)
|
||||||
+8
-8
@@ -8,14 +8,14 @@ import os
|
|||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from src.application import Application
|
from application import Application
|
||||||
from src.device import DeviceType
|
from device import DeviceType
|
||||||
from src.device.emulator import Emulator
|
from device.emulator import Emulator
|
||||||
from src.device.geny_aws import GenyAWS
|
from device.geny_aws import GenyAWS
|
||||||
from src.device.geny_saas import GenySAAS
|
from device.geny_saas import GenySAAS
|
||||||
from src.helper import convert_str_to_bool, get_env_value_or_raise
|
from helper import convert_str_to_bool, get_env_value_or_raise
|
||||||
from src.constants import ENV
|
from constants import ENV
|
||||||
from src.logger import log
|
from logger import log
|
||||||
|
|
||||||
log.init()
|
log.init()
|
||||||
logger = logging.getLogger("App")
|
logger = logging.getLogger("App")
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ EMULATOR_IMG_TYPE = "EMULATOR_IMG_TYPE"
|
|||||||
EMULATOR_NAME = "EMULATOR_NAME"
|
EMULATOR_NAME = "EMULATOR_NAME"
|
||||||
EMULATOR_NO_SKIN = "EMULATOR_NO_SKIN"
|
EMULATOR_NO_SKIN = "EMULATOR_NO_SKIN"
|
||||||
EMULATOR_SYS_IMG = "EMULATOR_SYS_IMG"
|
EMULATOR_SYS_IMG = "EMULATOR_SYS_IMG"
|
||||||
|
EMULATOR_CONFIG_PATH = "EMULATOR_CONFIG_PATH"
|
||||||
|
|
||||||
# Device (Genymotion - General)
|
# Device (Genymotion - General)
|
||||||
GENYMOTION_TEMPLATE_PATH = "GENYMOTION_TEMPLATE_PATH"
|
GENYMOTION_TEMPLATE_PATH = "GENYMOTION_TEMPLATE_PATH"
|
||||||
@@ -37,6 +38,7 @@ GENYMOTION_TEMPLATE_PATH = "GENYMOTION_TEMPLATE_PATH"
|
|||||||
# Device (Geny_SAAS)
|
# Device (Geny_SAAS)
|
||||||
GENY_SAAS_USER = "GENY_SAAS_USER"
|
GENY_SAAS_USER = "GENY_SAAS_USER"
|
||||||
GENY_SAAS_PASS = "GENY_SAAS_PASS"
|
GENY_SAAS_PASS = "GENY_SAAS_PASS"
|
||||||
|
GENY_AUTH_TOKEN = "GENY_AUTH_TOKEN"
|
||||||
GENY_SAAS_TEMPLATE_FILE_NAME = "saas.json"
|
GENY_SAAS_TEMPLATE_FILE_NAME = "saas.json"
|
||||||
|
|
||||||
# Device (Geny_AWS)
|
# Device (Geny_AWS)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import time
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from src.helper import convert_str_to_bool, get_env_value_or_raise
|
from helper import convert_str_to_bool, get_env_value_or_raise
|
||||||
from src.constants import DEVICE, ENV
|
from constants import DEVICE, ENV
|
||||||
|
|
||||||
|
|
||||||
class DeviceType(Enum):
|
class DeviceType(Enum):
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import time
|
|||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from src.device import Device, DeviceType
|
from device import Device, DeviceType
|
||||||
from src.helper import convert_str_to_bool, get_env_value_or_raise, symlink_force
|
from helper import convert_str_to_bool, get_env_value_or_raise, symlink_force
|
||||||
from src.constants import ENV, UTF8
|
from constants import ENV, UTF8
|
||||||
|
|
||||||
|
|
||||||
class Emulator(Device):
|
class Emulator(Device):
|
||||||
@@ -22,7 +22,8 @@ class Emulator(Device):
|
|||||||
"Samsung Galaxy S7 Edge",
|
"Samsung Galaxy S7 Edge",
|
||||||
"Samsung Galaxy S8",
|
"Samsung Galaxy S8",
|
||||||
"Samsung Galaxy S9",
|
"Samsung Galaxy S9",
|
||||||
"Samsung Galaxy S10"
|
"Samsung Galaxy S10",
|
||||||
|
"Pixel C"
|
||||||
)
|
)
|
||||||
|
|
||||||
API_LEVEL = {
|
API_LEVEL = {
|
||||||
@@ -30,7 +31,8 @@ class Emulator(Device):
|
|||||||
"10.0": "29",
|
"10.0": "29",
|
||||||
"11.0": "30",
|
"11.0": "30",
|
||||||
"12.0": "32",
|
"12.0": "32",
|
||||||
"13.0": "33"
|
"13.0": "33",
|
||||||
|
"14.0": "34"
|
||||||
}
|
}
|
||||||
|
|
||||||
adb_name_id = 5554
|
adb_name_id = 5554
|
||||||
@@ -106,6 +108,30 @@ class Emulator(Device):
|
|||||||
symlink_force(path_device_profile_source, self.path_device_profile_target)
|
symlink_force(path_device_profile_source, self.path_device_profile_target)
|
||||||
self.logger.info("Samsung device profile is linked")
|
self.logger.info("Samsung device profile is linked")
|
||||||
|
|
||||||
|
def _use_override_config(self) -> None:
|
||||||
|
override_confg_path = os.getenv(ENV.EMULATOR_CONFIG_PATH)
|
||||||
|
|
||||||
|
if override_confg_path is None:
|
||||||
|
self.logger.info(f"The environment variable 'EMULATOR_CONFIG_PATH' is not set")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logger.info(f"Environment variable 'EMULATOR_CONFIG_PATH' found: {override_confg_path}")
|
||||||
|
|
||||||
|
if not os.path.isfile(override_confg_path):
|
||||||
|
self.logger.warning(f"Source file '{override_confg_path}' does not exist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.access(override_confg_path, os.R_OK):
|
||||||
|
self.logger.warning(f"Source file '{override_confg_path}' is not readable.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(override_confg_path, 'r') as src, open(self.path_emulator_config, 'a') as dst:
|
||||||
|
dst.write(src.read())
|
||||||
|
self.logger.info(f"Content from '{override_confg_path}' successfully appended to '{self.path_emulator_config}'.")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"An error occurred while copying file content: {e}")
|
||||||
|
|
||||||
def _add_skin(self) -> None:
|
def _add_skin(self) -> None:
|
||||||
device_skin_path = os.path.join(
|
device_skin_path = os.path.join(
|
||||||
self.path_emulator_skins, "{fn}".format(fn=self.file_name))
|
self.path_emulator_skins, "{fn}".format(fn=self.file_name))
|
||||||
@@ -130,6 +156,7 @@ class Emulator(Device):
|
|||||||
self.logger.info(f"Command to create emulator: '{creation_cmd}'")
|
self.logger.info(f"Command to create emulator: '{creation_cmd}'")
|
||||||
subprocess.check_call(creation_cmd, shell=True)
|
subprocess.check_call(creation_cmd, shell=True)
|
||||||
self._add_skin()
|
self._add_skin()
|
||||||
|
self._use_override_config()
|
||||||
self.logger.info(f"{self.device_type} is created!")
|
self.logger.info(f"{self.device_type} is created!")
|
||||||
|
|
||||||
def change_permission(self) -> None:
|
def change_permission(self) -> None:
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from src.device import Genymotion, DeviceType
|
from device import Genymotion, DeviceType
|
||||||
from src.helper import get_env_value_or_raise
|
from helper import get_env_value_or_raise
|
||||||
from src.constants import ENV, UTF8
|
from constants import ENV, UTF8
|
||||||
|
|
||||||
|
|
||||||
class GenyAWS(Genymotion):
|
class GenyAWS(Genymotion):
|
||||||
|
|||||||
+104
-35
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import concurrent.futures
|
||||||
|
import uuid
|
||||||
|
|
||||||
from src.device import Genymotion, DeviceType
|
from device import Genymotion, DeviceType
|
||||||
from src.helper import get_env_value_or_raise
|
from helper import get_env_value_or_raise
|
||||||
from src.constants import ENV, UTF8
|
from constants import ENV, UTF8
|
||||||
|
|
||||||
|
|
||||||
class GenySAAS(Genymotion):
|
class GenySAAS(Genymotion):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -12,20 +14,26 @@ class GenySAAS(Genymotion):
|
|||||||
self.logger = logging.getLogger(self.__class__.__name__)
|
self.logger = logging.getLogger(self.__class__.__name__)
|
||||||
self.device_type = DeviceType.GENY_SAAS.value
|
self.device_type = DeviceType.GENY_SAAS.value
|
||||||
self.created_devices = []
|
self.created_devices = []
|
||||||
|
|
||||||
def login(self) -> None:
|
def login(self) -> None:
|
||||||
user = get_env_value_or_raise(ENV.GENY_SAAS_USER)
|
if os.getenv(ENV.GENY_AUTH_TOKEN):
|
||||||
password = get_env_value_or_raise(ENV.GENY_SAAS_PASS)
|
auth_token = get_env_value_or_raise(ENV.GENY_AUTH_TOKEN)
|
||||||
subprocess.check_call(f"gmsaas auth login {user} {password} > /dev/null 2>&1", shell=True)
|
subprocess.check_call(f"gmsaas auth token {auth_token} > /dev/null 2>&1", shell=True)
|
||||||
|
else:
|
||||||
|
user = get_env_value_or_raise(ENV.GENY_SAAS_USER)
|
||||||
|
password = get_env_value_or_raise(ENV.GENY_SAAS_PASS)
|
||||||
|
subprocess.check_call(f"gmsaas auth login {user} {password} > /dev/null 2>&1", shell=True)
|
||||||
self.logger.info("successfully logged in!")
|
self.logger.info("successfully logged in!")
|
||||||
|
|
||||||
def create(self) -> None:
|
def create(self) -> None:
|
||||||
super().create()
|
super().create()
|
||||||
|
|
||||||
|
# Collect all items first
|
||||||
|
items = []
|
||||||
for item in self.get_data_from_template(ENV.GENY_SAAS_TEMPLATE_FILE_NAME):
|
for item in self.get_data_from_template(ENV.GENY_SAAS_TEMPLATE_FILE_NAME):
|
||||||
name = ""
|
name = ""
|
||||||
template = ""
|
template = ""
|
||||||
local_port = ""
|
local_port = ""
|
||||||
|
|
||||||
# implement like this because local_port param is not a must
|
# implement like this because local_port param is not a must
|
||||||
for k, v in item.items():
|
for k, v in item.items():
|
||||||
if k.lower() == "name":
|
if k.lower() == "name":
|
||||||
@@ -36,37 +44,98 @@ class GenySAAS(Genymotion):
|
|||||||
local_port = v
|
local_port = v
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"'{k}' is not supported! Please check the documentation!")
|
self.logger.warning(f"'{k}' is not supported! Please check the documentation!")
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
import uuid
|
|
||||||
name = str(uuid.uuid4())
|
name = str(uuid.uuid4())
|
||||||
|
|
||||||
if not template:
|
if not template:
|
||||||
self.shutdown_and_logout()
|
self.shutdown_and_logout()
|
||||||
raise RuntimeError(f"'template' is a must parameter and not given!")
|
raise RuntimeError(f"'template' is a must parameter and not given!")
|
||||||
else:
|
|
||||||
self.logger.info(f"name: {name}, template: {template}")
|
items.append({
|
||||||
creation_cmd = f"gmsaas instances start {template} {name}"
|
'name': name,
|
||||||
try:
|
'template': template,
|
||||||
instance_id = subprocess.check_output(creation_cmd.split()).decode(UTF8).replace("\n", "")
|
'local_port': local_port
|
||||||
created_device = {f"{name}": {instance_id}}
|
})
|
||||||
self.created_devices.append(created_device)
|
|
||||||
additional_args = ""
|
# Start all devices in parallel
|
||||||
if local_port:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
|
||||||
additional_args = f"--adb-serial-port {local_port}"
|
try:
|
||||||
connect_cmd = f"gmsaas instances adbconnect {instance_id} {additional_args}"
|
# Submit all tasks
|
||||||
subprocess.check_call(f"{connect_cmd}", shell=True)
|
future_to_item = {executor.submit(self.create_instance, item): item for item in items}
|
||||||
except Exception as e:
|
|
||||||
self.shutdown_and_logout()
|
# Collect results as they complete
|
||||||
self.logger.error(e)
|
for future in concurrent.futures.as_completed(future_to_item):
|
||||||
exit(1)
|
item = future_to_item[future]
|
||||||
|
try:
|
||||||
|
created_device = future.result()
|
||||||
|
self.created_devices.append(created_device)
|
||||||
|
self.logger.info(f"Successfully created device: {created_device}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Instance creation failed for {item['name']}: {e}")
|
||||||
|
self.shutdown_and_logout()
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.shutdown_and_logout()
|
||||||
|
self.logger.error(f"Parallel execution failed: {e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
def create_instance(self, item_data):
|
||||||
|
"""Create a single instance and return the result"""
|
||||||
|
name = item_data['name']
|
||||||
|
template = item_data['template']
|
||||||
|
local_port = item_data['local_port']
|
||||||
|
|
||||||
|
self.logger.info(f"name: {name}, template: {template}")
|
||||||
|
creation_cmd = f"gmsaas instances start {template} {name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance_id = subprocess.check_output(creation_cmd.split()).decode(UTF8).replace("\n", "")
|
||||||
|
|
||||||
|
# Connect to ADB
|
||||||
|
additional_args = ""
|
||||||
|
if local_port:
|
||||||
|
additional_args = f"--adb-serial-port {local_port}"
|
||||||
|
connect_cmd = f"gmsaas instances adbconnect {instance_id} {additional_args}"
|
||||||
|
subprocess.check_call(f"{connect_cmd}", shell=True)
|
||||||
|
|
||||||
|
return {f"{name}": instance_id}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to create instance {name}: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def stop_instance(self, device_info):
|
||||||
|
"""Stop a single instance"""
|
||||||
|
for name, instance_id in device_info.items():
|
||||||
|
try:
|
||||||
|
subprocess.check_call(f"gmsaas instances stop {instance_id}", shell=True)
|
||||||
|
self.logger.info(f"device '{name}' is successfully removed!")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to stop device '{name}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def shutdown_and_logout(self) -> None:
|
def shutdown_and_logout(self) -> None:
|
||||||
if bool(self.created_devices):
|
if bool(self.created_devices):
|
||||||
self.logger.info("Created device(s) will be removed!")
|
self.logger.info("Created device(s) will be removed!")
|
||||||
for d in self.created_devices:
|
|
||||||
for n, i in d.items():
|
# Stop all devices in parallel
|
||||||
subprocess.check_call(f"gmsaas instances stop {i}", shell=True)
|
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
|
||||||
self.logger.info(f"device '{n}' is successfully removed!")
|
# Submit all stop tasks
|
||||||
subprocess.check_call("gmsaas auth logout", shell=True)
|
futures = [executor.submit(self.stop_instance, device) for device in self.created_devices]
|
||||||
self.logger.info("successfully logged out!")
|
|
||||||
|
# Wait for all to complete
|
||||||
|
for future in concurrent.futures.as_completed(futures):
|
||||||
|
try:
|
||||||
|
future.result()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error during device shutdown: {e}")
|
||||||
|
|
||||||
|
# Logout after all devices are stopped
|
||||||
|
if os.getenv(ENV.GENY_AUTH_TOKEN):
|
||||||
|
subprocess.check_call("gmsaas auth reset", shell=True)
|
||||||
|
else:
|
||||||
|
subprocess.check_call("gmsaas auth logout", shell=True)
|
||||||
|
self.logger.info("successfully logged out!")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging.config
|
import logging.config
|
||||||
|
|
||||||
from src.logger import LOGGING_FILE
|
from logger import LOGGING_FILE
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from src.constants import ENV
|
from constants import ENV
|
||||||
from src.tests import BaseTest
|
from tests import BaseTest
|
||||||
|
|
||||||
|
|
||||||
class BaseDeviceTest(BaseTest):
|
class BaseDeviceTest(BaseTest):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from src.device import Device
|
from device import Device
|
||||||
from src.tests.device import BaseDeviceTest
|
from tests.device import BaseDeviceTest
|
||||||
|
|
||||||
|
|
||||||
class TestDevice(BaseDeviceTest):
|
class TestDevice(BaseDeviceTest):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from src.device.emulator import Emulator
|
from device.emulator import Emulator
|
||||||
from src.tests.device import BaseDeviceTest
|
from tests.device import BaseDeviceTest
|
||||||
|
|
||||||
|
|
||||||
class TestEmulator(BaseDeviceTest):
|
class TestEmulator(BaseDeviceTest):
|
||||||
@@ -52,29 +52,6 @@ class TestEmulator(BaseDeviceTest):
|
|||||||
def test_initialisation_device_exists(self):
|
def test_initialisation_device_exists(self):
|
||||||
self.assertEqual(self.emu.is_initialized(), True)
|
self.assertEqual(self.emu.is_initialized(), True)
|
||||||
|
|
||||||
@mock.patch("src.device.Device.set_status")
|
|
||||||
@mock.patch("src.device.emulator.Emulator._add_profile")
|
|
||||||
@mock.patch("subprocess.check_call")
|
|
||||||
@mock.patch("src.device.emulator.Emulator._add_skin")
|
|
||||||
@mock.patch("src.device.emulator.Emulator.is_initialized", mock.MagicMock(return_value=False))
|
|
||||||
def test_create_device_not_exist(self, mocked_status, mocked_profile, mocked_subprocess, mocked_skin):
|
|
||||||
self.emu.create()
|
|
||||||
self.assertEqual(mocked_status.called, True)
|
|
||||||
self.assertEqual(mocked_profile.called, True)
|
|
||||||
self.assertEqual(mocked_subprocess.called, True)
|
|
||||||
self.assertEqual(mocked_skin.called, True)
|
|
||||||
|
|
||||||
@mock.patch("src.device.Device.set_status")
|
|
||||||
@mock.patch("src.device.emulator.Emulator._add_profile")
|
|
||||||
@mock.patch("subprocess.check_call")
|
|
||||||
@mock.patch("src.device.emulator.Emulator._add_skin")
|
|
||||||
@mock.patch("src.device.emulator.Emulator.is_initialized", mock.MagicMock(return_value=True))
|
|
||||||
def test_create_device_exists(self, mocked_status, mocked_profile, mocked_subprocess, mocked_skin):
|
|
||||||
self.emu.create()
|
|
||||||
self.assertEqual(mocked_status.called, False)
|
|
||||||
self.assertEqual(mocked_profile.called, False)
|
|
||||||
self.assertEqual(mocked_subprocess.called, False)
|
|
||||||
|
|
||||||
def test_check_adb_command(self):
|
def test_check_adb_command(self):
|
||||||
with mock.patch("subprocess.check_output", mock.MagicMock(return_value="1".encode("utf-8"))):
|
with mock.patch("subprocess.check_output", mock.MagicMock(return_value="1".encode("utf-8"))):
|
||||||
self.emu.check_adb_command(
|
self.emu.check_adb_command(
|
||||||
@@ -85,3 +62,25 @@ class TestEmulator(BaseDeviceTest):
|
|||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
self.emu.check_adb_command(
|
self.emu.check_adb_command(
|
||||||
self.emu.ReadinessCheck.BOOTED, "mocked_command", "1", 3, 0)
|
self.emu.ReadinessCheck.BOOTED, "mocked_command", "1", 3, 0)
|
||||||
|
|
||||||
|
def test_use_override_config_no_env(self):
|
||||||
|
with mock.patch("os.getenv", return_value=None):
|
||||||
|
self.emu._use_override_config()
|
||||||
|
|
||||||
|
def test_use_override_config_file_not_exist(self):
|
||||||
|
with mock.patch("os.getenv", return_value="mock/path/to/config"):
|
||||||
|
with mock.patch("os.path.isfile", return_value=False):
|
||||||
|
self.emu._use_override_config()
|
||||||
|
|
||||||
|
def test_use_override_config_file_not_readable(self):
|
||||||
|
with mock.patch("os.getenv", return_value="mock/path/to/config"):
|
||||||
|
with mock.patch("os.path.isfile", return_value=True):
|
||||||
|
with mock.patch("os.access", return_value=False):
|
||||||
|
self.emu._use_override_config()
|
||||||
|
|
||||||
|
def test_use_override_config_malformed_content(self):
|
||||||
|
with mock.patch("os.getenv", return_value="mock/path/to/config"):
|
||||||
|
with mock.patch("os.path.isfile", return_value=True):
|
||||||
|
with mock.patch("os.access", return_value=True):
|
||||||
|
with mock.patch("builtins.open", mock.mock_open(read_data="malformed data")):
|
||||||
|
self.emu._use_override_config()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from src.helper import convert_str_to_bool, get_env_value_or_raise, symlink_force
|
from helper import convert_str_to_bool, get_env_value_or_raise, symlink_force
|
||||||
from src.tests import BaseTest
|
from tests import BaseTest
|
||||||
|
|
||||||
|
|
||||||
class TestHelperMethods(BaseTest):
|
class TestHelperMethods(BaseTest):
|
||||||
@@ -45,10 +45,6 @@ class TestHelperMethods(BaseTest):
|
|||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
get_env_value_or_raise("env_key02")
|
get_env_value_or_raise("env_key02")
|
||||||
|
|
||||||
def test_get_env_value_with_invalid_format(self):
|
|
||||||
with mock.patch("src.logger"):
|
|
||||||
get_env_value_or_raise(True)
|
|
||||||
|
|
||||||
def test_symlink(self):
|
def test_symlink(self):
|
||||||
s = os.path.join("source.txt")
|
s = os.path.join("source.txt")
|
||||||
t = os.path.join("target_file.txt")
|
t = os.path.join("target_file.txt")
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
FROM appium/appium:v2.2.2-p0
|
FROM appium/appium:v3.0.2-p1
|
||||||
|
|
||||||
LABEL maintainer "Budi Utomo <budtmo.os@gmail.com>"
|
ARG AUTHORS="Budi Utomo"
|
||||||
|
LABEL author="${AUTHORS} <budtmo.os@gmail.com>"
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -50,8 +50,8 @@ RUN apt-get -qqy update && apt-get -qqy install --no-install-recommends \
|
|||||||
#=======
|
#=======
|
||||||
# noVNC
|
# noVNC
|
||||||
#=======
|
#=======
|
||||||
ENV NOVNC_VERSION="1.4.0" \
|
ENV NOVNC_VERSION="1.6.0" \
|
||||||
WEBSOCKIFY_VERSION="0.11.0" \
|
WEBSOCKIFY_VERSION="0.13.0" \
|
||||||
OPT_PATH="/opt"
|
OPT_PATH="/opt"
|
||||||
RUN wget -nv -O noVNC.zip "https://github.com/novnc/noVNC/archive/refs/tags/v${NOVNC_VERSION}.zip" \
|
RUN wget -nv -O noVNC.zip "https://github.com/novnc/noVNC/archive/refs/tags/v${NOVNC_VERSION}.zip" \
|
||||||
&& unzip -x noVNC.zip \
|
&& unzip -x noVNC.zip \
|
||||||
@@ -83,8 +83,8 @@ ENV APP_PATH=${WORK_PATH}/${SCRIPT_PATH}
|
|||||||
RUN mkdir -p ${APP_PATH}
|
RUN mkdir -p ${APP_PATH}
|
||||||
COPY mixins ${APP_PATH}/mixins
|
COPY mixins ${APP_PATH}/mixins
|
||||||
COPY cli ${APP_PATH}/cli
|
COPY cli ${APP_PATH}/cli
|
||||||
RUN chown -R 1300:1301 ${APP_PATH} \
|
RUN --mount=type=secret,id=extension,dst=/tmp/extension.sh \
|
||||||
&& pip install --quiet -e ${APP_PATH}/cli
|
bash /tmp/extension.sh
|
||||||
|
|
||||||
#===================
|
#===================
|
||||||
# Configure OpenBox
|
# Configure OpenBox
|
||||||
@@ -99,6 +99,8 @@ ENV LOG_PATH=${WORK_PATH}/logs \
|
|||||||
WEB_LOG_PORT=9000
|
WEB_LOG_PORT=9000
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
RUN mkdir -p ${LOG_PATH}
|
RUN mkdir -p ${LOG_PATH}
|
||||||
|
RUN mkdir -p "${WORK_PATH}/.config/Android Open Source Project" \
|
||||||
|
&& echo "[General]\nshowNestedWarning=false\n" > "${WORK_PATH}/.config/Android Open Source Project/Emulator.conf"
|
||||||
|
|
||||||
#=========
|
#=========
|
||||||
# Run App
|
# Run App
|
||||||
|
|||||||
+3
-8
@@ -1,12 +1,7 @@
|
|||||||
ARG DOCKER_ANDROID_VERSION
|
ARG DOCKER_ANDROID_VERSION
|
||||||
FROM budtmo/docker-android:base_${DOCKER_ANDROID_VERSION}
|
FROM budtmo/docker-android:base_${DOCKER_ANDROID_VERSION}
|
||||||
|
|
||||||
#===================================================
|
ENV GMSAAS_CLI_VERSION="1.15.0"
|
||||||
# Install Genymotion CLI
|
|
||||||
# (for user management and deployment on Geny Cloud)
|
|
||||||
#===================================================
|
|
||||||
ENV GMSAAS_CLI_VERSION="1.7.1"
|
|
||||||
RUN pip install gmsaas==${GMSAAS_CLI_VERSION}
|
|
||||||
|
|
||||||
#================
|
#================
|
||||||
# Cloud Packages
|
# Cloud Packages
|
||||||
@@ -40,8 +35,8 @@ ENV APP_PATH=${WORK_PATH}/${SCRIPT_PATH}
|
|||||||
RUN mkdir -p ${APP_PATH}
|
RUN mkdir -p ${APP_PATH}
|
||||||
COPY mixins ${APP_PATH}/mixins
|
COPY mixins ${APP_PATH}/mixins
|
||||||
COPY cli ${APP_PATH}/cli
|
COPY cli ${APP_PATH}/cli
|
||||||
RUN chown -R 1300:1301 ${APP_PATH} \
|
RUN --mount=type=secret,id=extension,dst=/tmp/extension.sh \
|
||||||
&& pip install --quiet -e ${APP_PATH}/cli
|
bash /tmp/extension.sh
|
||||||
|
|
||||||
#===================================
|
#===================================
|
||||||
# Create Genymotion Template folder
|
# Create Genymotion Template folder
|
||||||
|
|||||||
@@ -57,5 +57,18 @@ Possible environment variable to configure the Emulator:
|
|||||||
|
|
||||||
The user can also pass needed arguments to android emulator through environment variable ***EMULATOR_ADDITIONAL_ARGS***. Please check [this page](https://developer.android.com/studio/run/emulator-commandline) for possible arguments that can be passed.
|
The user can also pass needed arguments to android emulator through environment variable ***EMULATOR_ADDITIONAL_ARGS***. Please check [this page](https://developer.android.com/studio/run/emulator-commandline) for possible arguments that can be passed.
|
||||||
|
|
||||||
|
EMULATOR - OVERRIDE CONFIG FILE
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
To utilize this feature, ensure the following setup:
|
||||||
|
|
||||||
|
- Set the config file path into environment variable EMULATOR_CONFIG_PATH, eg. `/tmp/emulator-override-config.ini` (the file name doesn't matter)
|
||||||
|
- Mount the file as Docker volume (parameter `-v` for Docker run command) to the path as you set into ENV
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d -p 6080:6080 -e /tmp/emulator-override-config.ini -v /path/on/your/machine/emulator-override-config.ini:/tmp/emulator-override-config.ini -e EMULATOR_DEVICE="Samsung Galaxy S10" -e WEB_VNC=true --device /dev/kvm --name android-container budtmo/docker-android:emulator_14.0
|
||||||
|
```
|
||||||
|
|
||||||
[<- BACK TO README](../README.md)
|
[<- BACK TO README](../README.md)
|
||||||
|
|||||||
@@ -27,11 +27,17 @@ List of Docker-Images
|
|||||||
|11.0|30|Normal|budtmo2/docker-android-pro:emulator_11.0|budtmo2/docker-android-pro:emulator_11.0_<release_version>|
|
|11.0|30|Normal|budtmo2/docker-android-pro:emulator_11.0|budtmo2/docker-android-pro:emulator_11.0_<release_version>|
|
||||||
|12.0|32|Normal|budtmo2/docker-android-pro:emulator_12.0|budtmo2/docker-android-pro:emulator_12.0_<release_version>|
|
|12.0|32|Normal|budtmo2/docker-android-pro:emulator_12.0|budtmo2/docker-android-pro:emulator_12.0_<release_version>|
|
||||||
|13.0|33|Normal|budtmo2/docker-android-pro:emulator_13.0|budtmo2/docker-android-pro:emulator_13.0_<release_version>|
|
|13.0|33|Normal|budtmo2/docker-android-pro:emulator_13.0|budtmo2/docker-android-pro:emulator_13.0_<release_version>|
|
||||||
|
|14.0|34|Normal|budtmo2/docker-android-pro:emulator_14.0|budtmo2/docker-android-pro:emulator_14.0_<release_version>|
|
||||||
|
|15.0|35|Normal|budtmo2/docker-android-pro:emulator_15.0|budtmo2/docker-android-pro:emulator_15.0_<release_version>|
|
||||||
|
|16.0|36|Normal|budtmo2/docker-android-pro:emulator_16.0|budtmo2/docker-android-pro:emulator_16.0_<release_version>|
|
||||||
|9.0|28|Headless|budtmo2/docker-android-pro:emulator_headless_9.0|budtmo2/docker-android-pro:emulator_headless_9.0_<release_version>|
|
|9.0|28|Headless|budtmo2/docker-android-pro:emulator_headless_9.0|budtmo2/docker-android-pro:emulator_headless_9.0_<release_version>|
|
||||||
|10.0|29|Headless|budtmo2/docker-android-pro:emulator_headless_10.0|budtmo2/docker-android-pro:emulator_headless_10.0_<release_version>|
|
|10.0|29|Headless|budtmo2/docker-android-pro:emulator_headless_10.0|budtmo2/docker-android-pro:emulator_headless_10.0_<release_version>|
|
||||||
|11.0|30|Headless|budtmo2/docker-android-pro:emulator_headless_11.0|budtmo2/docker-android-pro:emulator_headless_11.0_<release_version>|
|
|11.0|30|Headless|budtmo2/docker-android-pro:emulator_headless_11.0|budtmo2/docker-android-pro:emulator_headless_11.0_<release_version>|
|
||||||
|12.0|32|Headless|budtmo2/docker-android-pro:emulator_headless_12.0|budtmo2/docker-android-pro:emulator_headless_12.0_<release_version>|
|
|12.0|32|Headless|budtmo2/docker-android-pro:emulator_headless_12.0|budtmo2/docker-android-pro:emulator_headless_12.0_<release_version>|
|
||||||
|13.0|33|Headless|budtmo2/docker-android-pro:emulator_headless_13.0|budtmo2/docker-android-pro:emulator_headless_13.0_<release_version>|
|
|13.0|33|Headless|budtmo2/docker-android-pro:emulator_headless_13.0|budtmo2/docker-android-pro:emulator_headless_13.0_<release_version>|
|
||||||
|
|14.0|34|Headless|budtmo2/docker-android-pro:emulator_headless_14.0|budtmo2/docker-android-pro:emulator_headless_14.0_<release_version>|
|
||||||
|
|15.0|35|Headless|budtmo2/docker-android-pro:emulator_headless_15.0|budtmo2/docker-android-pro:emulator_headless_15.0_<release_version>|
|
||||||
|
|16.0|36|Headless|budtmo2/docker-android-pro:emulator_headless_16.0|budtmo2/docker-android-pro:emulator_headless_16.0_<release_version>|
|
||||||
|-|-|Selenium|budtmo2/docker-android-pro:selenium|budtmo2/docker-android-pro:selenium_<release_version>|
|
|-|-|Selenium|budtmo2/docker-android-pro:selenium|budtmo2/docker-android-pro:selenium_<release_version>|
|
||||||
|
|
||||||
***Note: Headless mode does not have any Web-UI***
|
***Note: Headless mode does not have any Web-UI***
|
||||||
@@ -55,6 +61,7 @@ Phone | Nexus 5
|
|||||||
Phone | Nexus One
|
Phone | Nexus One
|
||||||
Phone | Nexus S
|
Phone | Nexus S
|
||||||
Tablet | Nexus 7
|
Tablet | Nexus 7
|
||||||
|
Tablet | Pixel C
|
||||||
|
|
||||||
|
|
||||||
Proxy
|
Proxy
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
```
|
||||||
|
export USER="xxx"
|
||||||
|
export PASS="xxx"
|
||||||
|
|
||||||
|
docker run -d -p 4723:4723 -v ${PWD}/example/genycloud/saas.json:/home/androidusr/genymotion_template/saas.json -e DEVICE_TYPE=geny_saas -e GENY_SAAS_USER=${USER} -e GENY_SAAS_PASS=${PASS} -e APPIUM=true --name android-container budtmo/docker-android:genymotion
|
||||||
|
```
|
||||||
Genymotion Cloud
|
Genymotion Cloud
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@@ -9,12 +15,13 @@ You can use Genymotion Android virtual devices in the cloud. They are available
|
|||||||
Use [saas.json](../example/genymotion/saas.json) to define the devices that you want to use. You can specify the port on which the device will start so you don't need to change the device name in your tests every time you need to run those tests. Then run following command
|
Use [saas.json](../example/genymotion/saas.json) to define the devices that you want to use. You can specify the port on which the device will start so you don't need to change the device name in your tests every time you need to run those tests. Then run following command
|
||||||
|
|
||||||
```
|
```
|
||||||
export USER="xxx"
|
export AUTH_TOKEN="xxx"
|
||||||
export PASS="xxx"
|
|
||||||
|
|
||||||
docker run -d -p 4723:4723 -v ${PWD}/example/genycloud/saas.json:/home/androidusr/genymotion_template/saas.json -e DEVICE_TYPE=geny_saas -e GENY_SAAS_USER=${USER} -e GENY_SAAS_PASS=${PASS} -e APPIUM=true --name android-container budtmo/docker-android:genymotion
|
docker run -d -p 4723:4723 -v ${PWD}/example/genycloud/saas.json:/home/androidusr/genymotion_template/saas.json -e DEVICE_TYPE=geny_saas -e GENY_AUTH_TOKEN=${AUTH_TOKEN} -e APPIUM=true --name android-container budtmo/docker-android:genymotion
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Genymotion has deprecated credential based login since gmsaas 1.10.0, but if necessary, you can still provide them using `-e GENY_SAAS_USER=${USER} -e GENY_SAAS_PASS=${PASS}` instead of `-e GENY_AUTH_TOKEN=${AUTH_TOKEN}`.
|
||||||
|
|
||||||
The deployed device(s) are automatically connected with adb inside docker container. Stopping the emulator will remove all deployed device(s) on Genymotion SaaS and user will be logged out at the end.
|
The deployed device(s) are automatically connected with adb inside docker container. Stopping the emulator will remove all deployed device(s) on Genymotion SaaS and user will be logged out at the end.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user