Appium 容器化 和 持续集成
🍒

Appium 容器化 和 持续集成

Created
Oct 10, 2022 03:28 AM
Tags
│ Jenkinsfile // Jenkins 流水线配置 │ main-ci.py // CI 执行入口 │ pytest.ini // pytest 参数配置 │ requirements.txt // 第三方依赖 ├─case // 用例目录 │ └─test_<PLATFORM_NAME> // 平台用例目录 │ │ └─test_<PACKAGE_NAME> // 包用例目录 │ │ │ conftest.py // fixture 配置 │ │ │ test_<MODULE_NAME>.py // 模块用例 │ │ ├─common // 模块通用功能 │ │ ├─data // 模块配置 │ │ ├─page // 模块 page 封装 │ │ └─resource // 资源依赖 ├─ci // CI 配置目录 │ │ docker-compose.yml // Docker Compose Linux 配置 │ │ docker-compose-win.yml // Docker Compose Windows 配置 │ └─Dockerfile // Docker Python 容器配置 ├─config // 全局配置 ├─log // 测试过程中生成的 log ├─modules // 自动化组维护的模块 └─results // 测试数据目录
/* * 需要额外安装Jenkins插件: * - Extended Choice Parameter * - Subversion / Git * - Allure Jenkins */ def NODES = params.getOrDefault('NODES', 'master') // 执行节点 def PLATFORM = params.getOrDefault('PLATFORM', '') // 平台 class Globals { static String MODULES = '' // 测试用例 static LinkedHashMap PORTS = [ 'XX_COM': '/dev/null', 'YY_COM': '/dev/null', 'ZZ_COM': '/dev/null', ] // 外部设备端口 } // 获取用例模块 def getModules() { cases = sh( script: "ls ./case/test_${PLATFORM.toLowerCase()}/ |grep test_", returnStdout: true ) int start = 0 for (int i=0; i < cases.length(); i++ ) { if (cases.getAt(i) == '\n') { Globals.MODULES += "./case/test_${PLATFORM.toLowerCase()}/" + cases.substring(start, i) + ',' start = i + 1 } } } // 获取外部设备的端口 def getPorts() { // 刷新设备规则 sh 'udevadm control --reload-rules || true' sh 'udevadm trigger || true' // 等待10s再获取设备 sleep 10 ports = sh( script: "ls -l /dev/ |grep -E 'ttyUSB-|ttyACM-' | awk '{ print \$9,\$10,\$11 }'", returnStdout: true ) int start = 0 for (int i=0; i<ports.length(); i++) { if (ports.getAt(i) == '\n' && start < ports.length()) { def device = '/dev/' + ports.substring(start, i).split('->')[-1].strip() def link = ports.substring(start, i).split('->')[0].strip() switch(link) { case 'ttyUSB-XX': Globals.PORTS['XX_COM'] = device break case 'ttyUSB-YY': Globals.PORTS['YY_COM'] = device break case 'ttyUSB-ZZ': Globals.PORTS['ZZ_COM'] = device break default: println "unknown device: ${link}" } start = i + 1 } } } pipeline { agent { node { label NODES } } stages { stage('Set Environment Variables') { steps { script { getPorts() getModules() modules = input( message: '请选择测试用例', parameters: [ extendedChoice(name: 'MODULES', type: 'PT_MULTI_SELECT', value: Globals.MODULES, visibleItemCount: 20, description: '测试用例'), text(name: 'MODULES_EXTRA', description: '测试用例补充项,填写后和MODULES合并,多项英文逗号分隔') ] ) // 添加环境变量 def envs = "BUILD_URL=${env.BUILD_URL}\nCI=${env.CI}\n" for (_ in params) { envs += "${_.key}=${_.value.toString().split('\n').join('')}\n" } for (_ in Globals.PORTS) { envs += "${_.key}=${_.value.toString().split('\n').join('')}\n" } for (_ in modules) { envs += "${_.key}=${_.value.toString().split('\n').join('')}\n" } writeFile( file: '.env', text: envs ) } } } stage('Run Test Cases') { steps { script { // 打印环境变量 sh 'cat ./.env' // 宿主机adbd开启全网段监听 sh 'adb kill-server && adb -a -P 5037 nodaemon server >/tmp/adb_server_log 2>&1 &' // CI相关文件拷贝到根目录 sh "cp -rfp ./ci/* ." // 停止并删除全部容器服务 sh 'docker-compose rm -sf || true' // 清理测试报告数据 sh 'rm -rf ./allure-results || true' // 创建桥接网卡 sh 'docker network create --driver=bridge --subnet=172.31.0.0/16 --gateway=172.31.0.1 minicase || true' // 启动容器服务 sh 'docker-compose up --build -d' // 输出python-client容器的打印 sh 'docker-compose logs -f python-client' } } } } post { success { // 生成测试报告 allure( includeProperties: false, jdk: '', results: [[path: 'allure-results']] ) } always { // 停止服务并删除容器 sh 'docker-compose rm -sf || true' // 删除镜像和缓存 sh 'docker system prune -f || true' // 杀掉adbd sh 'adb kill-server' } } }
FROM python:3.9-bullseye@sha256:92c5a135b66384a322edc1dee5f9af8b245304b7618a246b2ca87442e2ee8cce ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone #===================== # Prepare Requirements #===================== WORKDIR /root COPY requirements.txt . RUN pip install --upgrade -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple && \ rm requirements.txt #=============================================== # Install Android Platform Tools For ADB Command #=============================================== # https://dl.google.com/android/repository/platform-tools_r33.0.1-linux.zip ENV SDK_VERSION=platform-tools_r33.0.1-linux ENV ANDROID_HOME=/root COPY bin/${SDK_VERSION}.zip $ANDROID_HOME/ RUN unzip $ANDROID_HOME/${SDK_VERSION}.zip && \ rm $ANDROID_HOME/${SDK_VERSION}.zip && \ chmod a+x -R $ANDROID_HOME && \ chown -R root:root $ANDROID_HOME # https://askubuntu.com/questions/885658/android-sdk-repositories-cfg-could-not-be-loaded RUN mkdir -p ~/.android && \ touch ~/.android/repositories.cfg ENV PATH=$PATH:$ANDROID_HOME/platform-tools #=========================== # Install OpenJDK and Allure #=========================== RUN sed -i -E 's/(deb|security).debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \ apt-get -y update && \ apt-get install -y --no-install-recommends \ openjdk-11-jdk-headless \ ffmpeg && \ rm -rf /var/lib/apt/lists/* # https://github.com/allure-framework/allure2/releases/download/2.17.3/allure-2.17.3.zip ENV ALLURE_VERSION=allure-2.17.3 ENV ALLURE_HOME=/root COPY bin/${ALLURE_VERSION}.zip $ALLURE_HOME/ RUN unzip $ALLURE_HOME/${ALLURE_VERSION}.zip && \ rm $ALLURE_HOME/${ALLURE_VERSION}.zip && \ chmod a+x -R $ALLURE_HOME && \ chown -R root:root $ALLURE_HOME ENV PATH=$PATH:$ALLURE_HOME/$ALLURE_VERSION/bin #================================ # Install Chrome Driver for Opera #================================ # https://registry.npmmirror.com/-/binary/chromedriver/2.35/chromedriver_linux64.zip ENV CHROME_DRIVER_VERSION=chromedriver_2.35_linux64 ENV CHROME_DRIVER_HOME=/root COPY bin/${CHROME_DRIVER_VERSION}.zip $CHROME_DRIVER_HOME/ RUN unzip ${CHROME_DRIVER_HOME}/${CHROME_DRIVER_VERSION}.zip && \ rm ${CHROME_DRIVER_HOME}/${CHROME_DRIVER_VERSION}.zip && \ chmod a+x -R $CHROME_DRIVER_HOME && \ chown -R root:root $CHROME_DRIVER_HOME ENV PATH=$PATH:$ALLURE_HOME #===================== # Prepare Project Code #===================== RUN mkdir -p /MiniCase WORKDIR /MiniCase COPY . .
version: '2.3' services: python-client: container_name: python-client build: . depends_on: appium-server: condition: service_healthy selenium-server: condition: service_healthy command: bash ./script/run.sh devices: - "${XX_COM}:/dev/ttyUSB-XX" # 外部设备映射 - "${YY_COM}:/dev/ttyUSB-YY" - "${ZZ_COM}:/dev/ttyUSB-ZZ" - "/dev/video0:/dev/video0" # 摄像头 environment: APPIUM_SERVER: appium-server:4723 SELENIUM_SERVER: selenium-server:4444 ADB_SERVER_SOCKET: tcp:172.31.0.1:5037 ANDROID_ADB_SERVER_HOST: 172.31.0.1 ANDROID_ADB_SERVER_PORT: 5037 env_file: .env volumes: - /root/.android:/root/.android - ${PWD}/allure-results:/MiniCase/allure-results privileged: true tty: true networks: - minicase appium-server: container_name: appium-server image: appium/appium environment: REMOTE_ADB: 'true' ANDROID_DEVICES: "${ADB_TV},${ADB_MOBILE}" ADB_SERVER_SOCKET: tcp:172.31.0.1:5037 RELAXED_SECURITY: 'true' volumes: - /root/.android:/root/.android healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:4723/wd/hub/status" ] interval: 20s timeout: 10s retries: 10 networks: - minicase selenium-server: container_name: selenium-server image: selenium/standalone-chrome shm_size: 2gb healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:4444/wd/hub/status" ] interval: 20s timeout: 10s retries: 10 networks: - minicase networks: minicase: external: true