diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 732aa2c7..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -.eslintrc.js -run/**/*.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index eff91318..ca4b6696 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,4 +22,5 @@ module.exports = { "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-assignment": "off", }, + ignorePatterns: ['avd/', '*.js', "run/**/*.js", ".eslintrc.js"] }; diff --git a/.prettierignore b/.prettierignore index 0015423d..c1a81272 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ *.js README.md -package.json \ No newline at end of file +package.json +/avd/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..073e83c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM android-emulator-base + + + +#========================= +# Copying Scripts to root +#========================= + +RUN chmod a+x /session-appium/docker/*.sh +RUN ln -s /session-appium/docker/dl.sh /usr/bin/dl && ln -s /session-appium/docker/test.sh /usr/bin/ci_test && ln -s /session-appium/docker/dl_and_test.sh /usr/bin/dl_and_test + + + +WORKDIR /session-appium + +EXPOSE 8080 + +COPY avd /root/.android/avd + +ENTRYPOINT [ "/usr/bin/supervisord", "-c", "/etc/supervisord_test.conf", "-n" ] diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 00000000..f10b7f30 --- /dev/null +++ b/Dockerfile.base @@ -0,0 +1,119 @@ +# syntax=docker.io/docker/dockerfile:1.7-labs + +FROM openjdk:18-ea-11-jdk-slim-bullseye + +LABEL maintainer "Audric Ackermann" + +ENV DEBIAN_FRONTEND noninteractive + +WORKDIR / +#============================= +# Install Dependenices +#============================= +SHELL ["/bin/bash", "-c"] + +RUN apt-get update +RUN apt install -y ca-certificates curl git cpu-checker supervisor vim bash wget unzip xvfb x11vnc fluxbox xterm novnc net-tools htop libpulse-dev libnss3 libxcursor1 libasound2 libqt5gui5 libc++-dev libxcb-cursor0 htop tree tar gzip + + +#============================== +# Android SDK ARGS +#============================== +ARG ARCH="x86_64" +ARG TARGET="google_apis_playstore" +ARG API_LEVEL="34" +ARG BUILD_TOOLS="34.0.0" +ARG ANDROID_ARCH=${ANDROID_ARCH_DEFAULT} +ARG ANDROID_API_LEVEL="android-${API_LEVEL}" +ARG ANDROID_APIS="${TARGET};${ARCH}" +ARG EMULATOR_PACKAGE="system-images;${ANDROID_API_LEVEL};${ANDROID_APIS}" +ARG PLATFORM_VERSION="platforms;${ANDROID_API_LEVEL}" +ARG BUILD_TOOL="build-tools;${BUILD_TOOLS}" +ARG ANDROID_CMD="commandlinetools-linux-11076708_latest.zip" +ARG ANDROID_SDK_PACKAGES="${EMULATOR_PACKAGE} ${PLATFORM_VERSION} ${BUILD_TOOL} platform-tools" + +#============================== +# Set JAVA_HOME - SDK +#============================== +ENV ANDROID_SDK_ROOT=/opt/android +ENV PATH "$PATH:$ANDROID_SDK_ROOT/cmdline-tools/tools:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/latform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$ANDROID_SDK_ROOT/platform-tools/" +ENV DOCKER="true" + +#============================================ +# Install required Android CMD-line tools +#============================================ +RUN wget https://dl.google.com/android/repository/${ANDROID_CMD} -P /tmp && \ + unzip -d $ANDROID_SDK_ROOT /tmp/$ANDROID_CMD && \ + mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/tools && cd $ANDROID_SDK_ROOT/cmdline-tools && mv NOTICE.txt source.properties bin lib tools/ && \ + cd $ANDROID_SDK_ROOT/cmdline-tools/tools && ls + +#============================================ +# Install required package using SDK manager +#============================================ +RUN yes Y | sdkmanager --licenses +RUN yes Y | sdkmanager --verbose --no_https ${ANDROID_SDK_PACKAGES} + +#============================================ +# Create required emulators +#============================================ +RUN adb devices # keep this one to make sure adb is initialized (and creates a dummy adbkey, then erase it) +RUN rm /root/.android/adbkey +COPY adbkey /root/.android/adbkey +COPY adbkey.pub /root/.android/adbkey.pub +RUN chmod 600 /root/.android/adbkey +RUN chmod 644 /root/.android/adbkey.pub +RUN adb devices + +ARG EMULATOR_NAME="emulator1" +ARG EMULATOR_DEVICE="pixel_6" # all emulators are created with the pixel 6 spec for now + +RUN yes | sdkmanager emulator +RUN echo "no" | avdmanager --verbose create avd --force --name "${EMULATOR_NAME}" --device "${EMULATOR_DEVICE}" --package "${EMULATOR_PACKAGE}" + +#========================== +# Install node & yarn berry +#========================== + +RUN curl -sL https://deb.nodesource.com/setup_18.x | bash && \ + apt-get -qqy install nodejs && npm install -g yarn && corepack enable && \ + yarn set version 4.1.1 + + +# Install websokify and noVNC +RUN curl -O https://bootstrap.pypa.io/get-pip.py && \ + python3 get-pip.py && \ + pip3 install --no-cache-dir \ + setuptools && \ + pip3 install -U https://github.com/novnc/websockify/archive/refs/tags/v0.11.0.tar.gz + +RUN wget -O x11vnc.zip https://github.com/x11vnc/noVNC/archive/refs/heads/x11vnc.zip && \ + unzip x11vnc.zip && mv noVNC-x11vnc /usr/local/noVNC/ && ls -la /usr/local/noVNC/utils && \ + (chmod a+x /usr/local/noVNC/utils/launch.sh || \ + (chmod a+x /usr/local/noVNC/utils/novnc_proxy && \ + ln -s -f /usr/local/noVNC/utils/novnc_proxy /usr/local/noVNC/utils/launch.sh)) && \ + rm -rf /tmp/* /var/tmp/* + + +ENV HOME=/root \ + DEBIAN_FRONTEND=noninteractive \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 \ + LC_ALL=C.UTF-8 \ + DISPLAY=:0.0 \ + DISPLAY_WIDTH=1920 \ + DISPLAY_HEIGHT=900 \ + RUN_XTERM=yes \ + RUN_FLUXBOX=yes + + +#========================== +# copy the appium current folder +#========================== + +COPY --exclude="node_modules" --exclude="avd" --exclude=".git" --exclude="etc" --exclude="config/local*" ./ /session-appium + + +COPY docker/etc /etc + + +EXPOSE 8080 \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 6810bdbd..7e34362b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,106 +1,56 @@ -# Android emulator Image +### Build the base image without the adb in it -The use of this Docker image simplifies the process of running an Android emulator within a Docker container. This can be achieved through a few basic commands or by utilizing a simple Docker compose file. The image includes the latest version of the Android SDK, as well as the Appium server, which allows for the execution of mobile automation tests. - for more info --> https://medium.com/@Amr.sa/running-android-emulator-in-a-docker-container-19ecb68e1909 +**This has to be done once on each computers you use this docker, and if you ever change the Dockerfile.base settings related to the avd.** -# Feature +Comment this line in the Dockerfile: +`COPY avd /root/.android/avd` -- Run android emulator in headless or in headed mode (through VNC) -- Support Appium driver -- Come with the latest JDK lts. +then run build both docker images (base and test one) so you can manually build a snapshot for your avds. (step 1) +```sh +clear; sudo docker build -t android-emulator-base -f Dockerfile.base . && sudo docker build -t android-emulator -f Dockerfile . && sudo docker run --privileged -it --device /dev/kvm -p 8080:8080 android-emulator +``` +Once that's done, open a browser to http://localhost:8080/vnc.html, you should see the novnc connect button. +Connect, and in the terminal opened, enter this (copy/paste doesn't work for now) +``` +./docker/start_emu_for_state.sh +``` -# Setup +Let the emulator start, and give it a few more minutes so the snapshot is as complete as possible. +Then, close manually the emulator, it should display the "saving state" dialog. +Once the state is saved, do on a host terminal from the root of the `appium` folder: +``` +sudo rm -rf avd; sudo docker cp $(sudo docker ps -q):/root/.android/avd/ ./avd # sudo docker ps -q returns the running container hash directly +``` -## Manual execution -Down below is the list of the main scripts to launch the relevant service, certain environment variables should be passed during starting the container. +Once that's done, stop the current docker container (ctrl-c should be enough). -1. **build the docker image :** +Then, uncomment the line +``` +COPY avd /root/.android/avd +``` - docker build -t android-emulator . +in the Dockerfile, and rebuild the 2 images +``` +clear; sudo docker build -t android-emulator-base -f Dockerfile.base . && sudo docker build -t android-emulator -f Dockerfile . # (step 1) +``` +Once that's done, you can start the docker with the apk to test with - OR for customized image +``` +sudo docker run --privileged -it --device /dev/kvm -e APK_URL='' -e NODE_CONFIG_ENV="ci" -e APK_TO_TEST_PATH="/session.apk" -p 8080:8080 android-emulator # (step 2) +``` - docker build \ - --build-arg ARCH=x86_64 \ - --build-arg TARGET=google_apis_playstore\ - --build-arg API_LEVEL=31 \ - --build-arg BUILD_TOOLS=31.0.0 \ - --build-arg EMULATOR_DEVICE="Nexus 6" \ - --build-arg EMULATOR_NAME=nexus \ - -t my-android-image . - -2. **Start your container:** +Then, reconnect via vnc http://localhost:8080/vnc.html, and run in the terminal +``` +./docker/start_4_emus.sh & +``` +The 4 emulators should start hopefully not too slowly. - docker run -it --privileged -d -p 5900:5900 --name androidContainer --privileged android-emulator +You can then trigger the dl of the APK (from the APK_TO_TEST_PATH env variable above) and the integration tests by running the command +`dl_and_test` -3. **Launch the appium session :** - docker exec --privileged -it androidContainer bash -c "appium -p 5900" +### Daily use - OR - - docker exec --privileged -it androidContainer bash -c "./start_appium.sh" - - -4. **Start the emulator in headless mode :** - - docker exec --privileged -it -e EMULATOR_TIMEOUT=300 androidContainer bash -c "./start_emu_headless.sh" - -5. **Starting VNC server:** - - docker exec --privileged -it androidContainer bash -c "./start_vnc.sh" - - - -## Launch emulator in headed mode - - -1. **The following command must be used to initiate the Docker container:** - - docker run -it -d -p 5900:5900 --name androidContainer -e VNC_PASSWORD=password --privileged android-emulator - -2. **Instantiate the VNC service by running:** - - docker exec --privileged -it androidContainer bash -c "./start_vnc.sh" - -3. **Connect to the VNC server via remmina or any VNC viewer, on:** - - localhost:5900 - -4. **Open dash terminal in vnc viewer and right the following command:** - - #: ./start_emu.sh - -vnc gif - -*Note: - - The "start_emu.sh" script will start the emulator in a visible mode, therefore it should not be used for integration with a pipeline such as GitHub Actions or CircleCI. Instead, use the "start_emu_headless.sh" script. - - By default, Running emulator is 'Nexus 6' (emulator name: nexus) (Android 13) - - It is not necessary to launch all services in the docker-compose file, instead you should only enable the services you require, and comment out the others in the file. - - -## Using Docker-compose - -The Docker Compose file simplifies the process of starting the service. It includes multiple services, such as launching the emulator with the Appium instance or launching the VNC server. You have the flexibility to enable or disable any service based on your needs. - - docker compose up - -## Environments - -**When manually starting the container, ensure to set the necessary environment variables for proper operation** - -| Environments | Description | Required | Service | -| ----------------- | -------------------------------------------------------------------------------------------------------- | ----------------- | -----------| -| APPIUM_PORT | Port for the appium instance | optional | Android | -| VNC_PASSWORD | Password needed to connect to VNC Server | optional | VNC | -| OSTYPE | linux or macos/darwin | optional | Android | -| EMULATOR_TIMEOUT | emulator booting up timeoue, default 240 second | optional | Android | -| HW_ACCEL_OVERRIDE | Pass aceel options e.g "-accel on" or "-aceel off" | optional | Android - -## Kill the container - -- **Run the following command to kill and remove the container:** - - docker rm -f androidContainer +No need to rebuild the avds every time you use the docker image/start integration tests, but you will have to rebuild both docker images after a `git pull` the session-appium repository (i.e. updating the integration tests themselves). That step should be very fast though, as everything should be cached by docker. I usually just have one big command to rebuild the changes and restart the container. \ No newline at end of file diff --git a/docker/dl_and_test.sh b/docker/dl_and_test.sh new file mode 100644 index 00000000..ab5c1683 --- /dev/null +++ b/docker/dl_and_test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex + +/session-appium/docker/dl.sh && /session-appium/docker/test.sh \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 8c662b2e..00000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# set -ex - -# RUN_FLUXBOX=${RUN_FLUXBOX:-yes} -# RUN_XTERM=${RUN_XTERM:-yes} - -# case $RUN_FLUXBOX in -# false|no|n|0) -# rm -f /app/conf.d/fluxbox.conf -# ;; -# esac - -# case $RUN_XTERM in -# false|no|n|0) -# rm -f /app/conf.d/xterm.conf -# ;; -# esac - -# exec supervisord -c /app/supervisord.conf \ No newline at end of file diff --git a/docker/start_emu.sh b/docker/start_emu.sh index f014acd9..a7f8ae4e 100644 --- a/docker/start_emu.sh +++ b/docker/start_emu.sh @@ -11,7 +11,7 @@ emulator_name=${EMULATOR_NAME} device_name=${DEVICE_NAME} function start_emulator() { - emulator -avd "${emulator_name}" -gpu off -read-only + emulator -avd "${emulator_name}" -read-only -gpu off printf "${G}==> ${BL}Emulator has ${YE}${EMULATOR_NAME} ${BL}started in headed mode! ${G}<==${NC}""\n" } diff --git a/docker/start_emu_for_state.sh b/docker/start_emu_for_state.sh index 0d89c842..67e0fb27 100644 --- a/docker/start_emu_for_state.sh +++ b/docker/start_emu_for_state.sh @@ -3,63 +3,10 @@ set -x emulator_name="emulator1" -function start_emulator_headless() { +function start_emulator_for_state() { rm -rf /root/.android/avd/emulator1.avd/snapshots/ # delete any saved snapshots - nohup emulator -avd "${emulator_name}" -no-boot-anim -gpu off -no-accel -no-window -no-snapshot & # no -read only flag here - printf "==> Emulator ${emulator_name} has started in headless mode! \n" + nohup emulator -avd "${emulator_name}" -gpu off -no-snapshot-load & # no -read only flag here + printf "==> Emulator ${emulator_name} has started IN HEADED mode! \n" } -function disable_animation() { - adb shell "settings put global window_animation_scale 0.0" - adb shell "settings put global transition_animation_scale 0.0" - adb shell "settings put global animator_duration_scale 0.0" -} - -function stop_emulator() { - printf "==> Stopping emulator for snapshot grab... \n" - adb -s emulator-5554 emu kill - printf "==> Emulator stopped \n" -} - - -function wait_emulator_to_be_ready() { - start_time=$(date +%s) - timeout=960 # the avd (without cpu virtualization takes 907s to boot up on my machine) - sleep_interval=5 - printf "==> Waiting booted emulator 🧐... \n" - - while [ "`adb shell getprop sys.boot_completed | tr -d '\r' `" != "1" ] ; do - adb devices # this script is broken for now, as it always reports devices as being offline during the docker build - current_time=$(date +%s) - elapsed_time=$((current_time - start_time)) - if [ $elapsed_time -gt $timeout ]; then - printf "==> Timeout after ${timeout} seconds elapsed 🕛.. \n" - break - fi - printf "==> Emulator not reported as booted yet 🧐... sleeping ${sleep_interval} before retry\n" - sleep $sleep_interval; - done - - if [ "`adb shell getprop sys.boot_completed | tr -d '\r' `" == "1" ] ; then - printf "==> Emulator is booted! \n" - fi -} - - -sleep 5 -start_emulator_headless -sleep 5 -wait_emulator_to_be_ready # wait for the emulator to report that the boot is complete -sleep 1 -disable_animation -sleep 1 - - -adb shell input keyevent 82 - -# add extra because it is still doing a bunch of things when booted without a saved state, and that'd time saved on restore -sleep 300 - -stop_emulator -sleep 10 # give some time to save the snapshot - +start_emulator_for_state diff --git a/docker/test.sh b/docker/test.sh new file mode 100644 index 00000000..d295b4a9 --- /dev/null +++ b/docker/test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex + +cd /session-appium && yarn install --immutable && yarn tsc && yarn test-no-retry "" \ No newline at end of file diff --git a/package.json b/package.json index 2f1b024b..6dec25bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "scripts": { - "lint": "yarn prettier --cache . --write \"**/*.+(ts)\" && yarn eslint . --cache --ignore-pattern '*.js'", + "lint": "yarn prettier --cache . --write '!./avd' \"**/*.+(ts)\" && yarn eslint . --cache ", "tsc": "yarn clean-tsc && tsc", "clean-tsc": "find run/test/specs -type f -name '*.js' -delete", "tsc-watch": "yarn clean-tsc && tsc -w", diff --git a/run/test/specs/utils/open_app.ts b/run/test/specs/utils/open_app.ts index d4630908..9603dc02 100644 --- a/run/test/specs/utils/open_app.ts +++ b/run/test/specs/utils/open_app.ts @@ -138,7 +138,7 @@ async function waitForEmulatorToBeRunning(emulatorName: string) { } while (Date.now() - start < 25000 && !found); if (!found) { - console.warn('isEmulatorRunning failed for 25s') + console.warn("isEmulatorRunning failed for 25s"); return; }